ferinの競プロ帳

競プロについてのメモ

COLOCON -Colopl programming contest 2018- Final C - スペースエクスプローラー高橋君

問題ページ
C - スペースエクスプローラー高橋君

CHTで解いてたけどmonotone minimaで解けるらしいので解くことにした

考えたこと

#include <bits/stdc++.h>

using namespace std;
using ll = long long;
#define int ll
using VI = vector<int>;
using VVI = vector<VI>;
using PII = pair<int, int>;

#define FOR(i, a, n) for (ll i = (ll)a; i < (ll)n; ++i)
#define REP(i, n) FOR(i, 0, n)
#define ALL(x) x.begin(), x.end()
#define PB push_back

const ll LLINF = (1LL<<60);
const int INF = (1LL<<30);
const int MOD = 1000000007;

template <typename T> T &chmin(T &a, const T &b) { return a = min(a, b); }
template <typename T> T &chmax(T &a, const T &b) { return a = max(a, b); }
template <typename T> bool IN(T a, T b, T x) { return a<=x&&x<b; }
template<typename T> T ceil(T a, T b) { return a/b + !!(a%b); }
template<class S,class T>
ostream &operator <<(ostream& out,const pair<S,T>& a){
  out<<'('<<a.first<<','<<a.second<<')';
  return out;
}
template<class T>
ostream &operator <<(ostream& out,const vector<T>& a){
  out<<'[';
  REP(i, a.size()) {out<<a[i];if(i!=a.size()-1)out<<',';}
  out<<']';
  return out;
}

int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0};

// monotone minima
// O(NlogN)
// verify: https://beta.atcoder.jp/contests/colopl2018-final-open/tasks/colopl2018_final_c
const int MAX_N = 200010;
int minima[MAX_N];
int a[MAX_N];

int f(int i, int j) { return a[j] + (j-i)*(j-i); }
void monotoneMinima(int ib, int ie, int jb, int je) {
  if(ib > ie) return;
  if(ib == ie) {
    int jm = jb;
    int fm = f(ib, jm), fj;
    for(int j=jb+1; j<=je; ++j) {
      if(fm > (fj = f(ib, j))) {
        fm = fj;
        jm = j;
      }
    }
    minima[ib] = jm;
    return;
  }
  int im = (ib+ie)/2;
  monotoneMinima(im, im, jb, je);
  monotoneMinima(ib, im-1, jb, minima[im]);
  monotoneMinima(im+1, ie, minima[im], je);
}

signed main(void)
{
  int n;
  cin >> n;
  REP(i, n) cin >> a[i];

  monotoneMinima(0, n-1, 0, n-1);
  REP(i, n) cout << f(i, minima[i]) << endl;

  return 0;
}

SPOJ BRKSTRNG - Breaking String

問題ページ
SPOJ.com - Problem BRKSTRNG

Mongeを使ったDP高速化の練習で解いた

考えたこと

  • 解説を読む
  • X(i,j) = ([i,j)のコスト) みたいな感じで持ちたい
  • W(i,j)は[i,j)を分割するのにかかるコストだから区間の幅でよさげ
  • DP漸化式がKnuth-Yao speedupの形になる
  • Mongeの証明がよくわからないけど投げたら通った(は?)

計算量10^8で定数もそこそこありそうでやばそうな雰囲気を感じたが1.98secで通ったのでセーフ(ア

#include <bits/stdc++.h>

using namespace std;
using ll = long long;
#define int ll
using VI = vector<int>;
using VVI = vector<VI>;
using PII = pair<int, int>;

#define FOR(i, a, n) for (ll i = (ll)a; i < (ll)n; ++i)
#define REP(i, n) FOR(i, 0, n)
#define ALL(x) x.begin(), x.end()
#define PB push_back

const ll LLINF = (1LL<<60);
const int INF = (1LL<<30);
const int MOD = 1000000007;

template <typename T> T &chmin(T &a, const T &b) { return a = min(a, b); }
template <typename T> T &chmax(T &a, const T &b) { return a = max(a, b); }
template <typename T> bool IN(T a, T b, T x) { return a<=x&&x<b; }
template<typename T> T ceil(T a, T b) { return a/b + !!(a%b); }
template<class S,class T>
ostream &operator <<(ostream& out,const pair<S,T>& a){
  out<<'('<<a.first<<','<<a.second<<')';
  return out;
}
template<class T>
ostream &operator <<(ostream& out,const vector<T>& a){
  out<<'[';
  REP(i, a.size()) {out<<a[i];if(i!=a.size()-1)out<<',';}
  out<<']';
  return out;
}

int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0};

signed main(void)
{
  int n, m;
  while(cin >> n >> m) {
    VI a(m);
    REP(i, m) cin >> a[i];

    // X(i,j) = ( [i,j)のコスト )
    // W(i,j) = ( iからjの区間の幅 )
    // K(i,j) = argmin_{i<=s<j} (X(i,s) + X(s,j))
    VVI W(m+5, VI(m+5, 0)), X(m+5, VI(m+5, 0)), K(m+5, VI(m+5, 0));
    REP(i, m+1) FOR(j, i+1, m+1) {
      W[i][j] = (j==m?n:a[j]) - (i==0?0:a[i-1]);
    }
    REP(i, m+1) REP(j, m+1) {
      X[i][j] = (i==j ? 0 : INF);
      K[i][j] = (i==j ? i : 0);
    }

    for(int w=1; w<=m; ++w) {
      for(int i=0, j=i+w; (j=i+w) <= m; ++i) {
        // K(i,j)の単調性から範囲が限定できる
        for(int r = K[i][j-1]; r <= K[i+1][j]; ++r) {
          int c = X[i][r] + X[r+1][j] + W[i][j];
          if(X[i][j] > c) { X[i][j] = c; K[i][j] = r; }
        }
      }
    }

    cout << X[0][m] << endl;
  }

  return 0;
}

SRM659 div1 easy ApplesAndOrangesEasy

考えたこと

  • 問題文を理解するのに時間がかかる
  • i番目をりんごにできるならりんごにせずに後ろに回した方がいい場合はなさそう
  • 貪欲に前からりんごにできるならりんごにするみたいなのを書く
  • O(K)で区間のりんごの数を数えられるので合計O(NK)で解けそう

区間のりんごの数を求めるところで区間をスライドしていく感じで書いたらindexバグらせて実装遅くなってしまった…
実装する内容自体は簡単だったし反省

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
typedef vector<int> VI;
typedef vector<VI> VVI;
typedef vector<ll> VL;
typedef vector<VL> VVL;
typedef pair<int, int> PII;

#define FOR(i, a, n) for (ll i = (ll)a; i < (ll)n; ++i)
#define REP(i, n) FOR(i, 0, n)
#define ALL(x) x.begin(), x.end()
#define IN(a, b, x) (a<=x&&x<b)
#define MP make_pair
#define PB push_back
const int INF = (1LL<<30);
const ll LLINF = (1LL<<60);
const double PI = 3.14159265359;
const double EPS = 1e-12;
const int MOD = 1000000007;
//#define int ll

template <typename T> T &chmin(T &a, const T &b) { return a = min(a, b); }
template <typename T> T &chmax(T &a, const T &b) { return a = max(a, b); }

int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0};

class ApplesAndOrangesEasy {
   public:
   int maximumApples(int N, int K, vector <int> info)
  {
    VI dp(N, 0);
    for(int i: info) dp[i-1] = 1;

    REP(i, N) {
      int now = 0;
      bool flag = true;
      REP(j, K) if(i-j >= 0) {
        now += dp[i-j];
        if(now >= K/2) flag = false;
      }
      FOR(j, 1, K) if(i+j < N) {
        if(i+j-K >= 0) now -= dp[i+j-K];
        now += dp[i+j];
        if(now >= K/2) flag = false;
      }
      if(flag) dp[i] = 1;
    }

    int ret = 0;
    REP(i, N) ret += dp[i];
    return ret;
  }
};

SRM658 div1 easy OddEvenTree

考えたこと

  • よくわからないので実験をする
  • ある頂点からOな頂点ならEOの列を反転した列になる
  • 距離が1ずれるんだからそれはそう
  • この条件で可能どうかの判定はできそうな気持ちになる
  • 条件を満たしていれば適当に構築してえい

サンプルが弱すぎるだろ
サンプルエスパー警戒なんだろうか

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
typedef vector<int> VI;
typedef vector<VI> VVI;
typedef vector<ll> VL;
typedef vector<VL> VVL;
typedef pair<int, int> PII;

#define FOR(i, a, n) for (ll i = (ll)a; i < (ll)n; ++i)
#define REP(i, n) FOR(i, 0, n)
#define ALL(x) x.begin(), x.end()
#define IN(a, b, x) (a<=x&&x<b)
#define MP make_pair
#define PB push_back
const int INF = (1LL<<30);
const ll LLINF = (1LL<<60);
const double PI = 3.14159265359;
const double EPS = 1e-12;
const int MOD = 1000000007;
//#define int ll

template <typename T> T &chmin(T &a, const T &b) { return a = min(a, b); }
template <typename T> T &chmax(T &a, const T &b) { return a = max(a, b); }

int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0};

class OddEvenTree {
   public:
   vector <int> getTree(vector <string> x)
  {
    int n = x.size();
    string s1 = x[0], s2 = "?";
    VI v1, v2;
    REP(i, n) {
      if(x[i] == s1) {
        v1.PB(i);
      } else {
        if(s2 == "?") s2 = x[i];
        if(x[i] == s2) v2.PB(i);
        else return {-1};
      }
    }

    REP(i, n) if(s1[i] == s2[i]) return {-1};
    REP(i, v2.size()) if(s1[v2[i]] != 'O') return {-1};
    if(s2 == "?") return {-1};

    VI ans;
    REP(i, v2.size()) {
      ans.PB(v1[0]);
      ans.PB(v2[i]);
    }
    // v2[0] と v1[i]
    FOR(i, 1, v1.size()) {
      ans.PB(v2[0]);
      ans.PB(v1[i]);
    }
    return ans;
  }
};

SRM657 div1 easy ProblemSets

考えたこと

  • EMでEとして使う問題数をw、Mとして使う問題数をx、MHでMとして使う問題をy、Hとして使う問題数をzとする
  • min(E+w, M+x+y, H+z)を最大化すればよい
  • 最小の最大化はにぶたんをしたくなる
  • 問題セットの数cntを作れるかの判定をしたい
  • Eがcntより小さければEMからその分Eに使うしかない、Hについても同様
  • EとHに使わないといけない問題を使ったあとMに使える問題数がcnt以上ならcnt個はつくれる
  • O(1)で判定ができるので二分探索ができた

二分探索がすんなり思いついてよかった

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
typedef vector<int> VI;
typedef vector<VI> VVI;
typedef vector<ll> VL;
typedef vector<VL> VVL;
typedef pair<int, int> PII;

#define FOR(i, a, n) for (ll i = (ll)a; i < (ll)n; ++i)
#define REP(i, n) FOR(i, 0, n)
#define ALL(x) x.begin(), x.end()
#define IN(a, b, x) (a<=x&&x<b)
#define MP make_pair
#define PB push_back
const int INF = (1LL<<30);
const ll LLINF = (1LL<<60);
const double PI = 3.14159265359;
const double EPS = 1e-12;
const int MOD = 1000000007;
//#define int ll

template <typename T> T &chmin(T &a, const T &b) { return a = min(a, b); }
template <typename T> T &chmax(T &a, const T &b) { return a = max(a, b); }

int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0};

class ProblemSets {
   public:
   long long maxSets(long long E, long long EM, long long M, long long MH, long long H)
  {
    ll lb = 0, ub = 2e18;

    auto check = [&](ll mid) -> bool {
      ll a = E, b = EM, c = M, d = MH, e = H;
      if(a < mid) {
        if(b < mid-a) return false;
        b -= mid - a;
        a = mid;
      }
      if(e < mid) {
        if(d < mid-e) return false;
        d -= mid-e;
        e = mid;
      }
      return b+c+d >= mid;
    };

    while(ub-lb > 1) {
      ll mid = (lb+ub)/2;
      // cout << lb << " " << mid << " " << ub << endl;
      if(check(mid)) {
        lb = mid;
      } else {
        ub = mid;
      }
    }

    return lb;
  }
};

SRM655 div1 easy BichromePainting

考えたこと

  • どんなマスなら白から黒に変えられるか考える
  • そのマスの右と下にkマスずつ余裕があれば白から黒にできる
  • 各方向からn-k列は何であっても実現できそうなので残りのk列が実現できるか判定できればよさそう
  • n-k列の方が埋まってたらk列の方が黒く塗れる位置が変化したり異常に判定が難しい
  • 何も思いつかないので問題文を読み返すとn<=50じゃなくてn<=20
  • 1列の情報を覚えとくbitDPみたいなのをやろうとしたけど1列だけ覚えたところでどうしようもなさそう
  • 何もわからない
    -------解説を見た-------
  • 逆に戻していく操作を考える
  • k*kマスで全部WかBなら塗る前の色がどんな状態だろうと関係ない
  • O(n^6)はじめて見た気がする

逆の操作を考えるのは定番中の定番なのに思いつかなかったのはだめ

学び

  • n=20ならO(n^6)が通る
#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
typedef vector<int> VI;
typedef vector<VI> VVI;
typedef vector<ll> VL;
typedef vector<VL> VVL;
typedef pair<int, int> PII;

#define FOR(i, a, n) for (ll i = (ll)a; i < (ll)n; ++i)
#define REP(i, n) FOR(i, 0, n)
#define ALL(x) x.begin(), x.end()
#define IN(a, b, x) (a<=x&&x<b)
#define MP make_pair
#define PB push_back
const int INF = (1LL<<30);
const ll LLINF = (1LL<<60);
const double PI = 3.14159265359;
const double EPS = 1e-12;
const int MOD = 1000000007;
//#define int ll

template <typename T> T &chmin(T &a, const T &b) { return a = min(a, b); }
template <typename T> T &chmax(T &a, const T &b) { return a = max(a, b); }

int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0};

class BichromePainting {
   public:
   string isThatPossible(vector <string> board, int k)
  {
    int n = board.size();

    bool update = true;
    while(update) {
      update = false;
      REP(y, n) REP(x, n) {
        if(y+k-1 >= n || x+k-1 >= n) continue;
        // 範囲内がwだけかbだけ
        bool w=false, b=false;
        REP(i, k) REP(j, k) {
          if(board[i+y][j+x] == 'W') w=true;
          if(board[i+y][j+x] == 'B') b=true;
        }
        if((w&&b) || (w==0&&b==0)) continue;
        REP(i, k) REP(j, k) {
          board[i+y][j+x] = '?';
        }
        update = true;
      }
    }

    // 全部?ならPossible
    REP(i, n) REP(j, n) if(board[i][j] != '?') return "Impossible";
    return "Possible";
  }
};

square869120Contest #4 D - Driving on a Tree

問題ページ
D: Driving on a Tree - square869120Contest #4 | AtCoder

全方位木DPに慣れるために解いた

考えたこと

  • 頂点vからどこかの子に移動したら他の頂点vの子には絶対に到達しない
  • d[i] = iを根とする部分木でiからスタートしたときの動く回数の期待値 をもちたくなる
  • d_par で pを根とするの部分木で動く回数を渡せば全方位木DPできますね
  • 書くと通った

20分くらいですんなり書けてよかった
d[i]とd_parをどうするか考えれば実装自体は大体一緒

#include <bits/stdc++.h>

using namespace std;
using ll = long long;
#define int ll
using VI = vector<int>;
using VVI = vector<VI>;
using PII = pair<int, int>;

#define FOR(i, a, n) for (ll i = (ll)a; i < (ll)n; ++i)
#define REP(i, n) FOR(i, 0, n)
#define ALL(x) x.begin(), x.end()
#define PB push_back

const ll LLINF = (1LL<<60);
const int INF = (1LL<<30);
const int MOD = 1000000007;

template <typename T> T &chmin(T &a, const T &b) { return a = min(a, b); }
template <typename T> T &chmax(T &a, const T &b) { return a = max(a, b); }
template <typename T> bool IN(T a, T b, T x) { return a<=x&&x<b; }
template<typename T> T ceil(T a, T b) { return a/b + !!(a%b); }
template<class S,class T>
ostream &operator <<(ostream& out,const pair<S,T>& a){
  out<<'('<<a.first<<','<<a.second<<')';
  return out;
}
template<class T>
ostream &operator <<(ostream& out,const vector<T>& a){
  out<<'[';
  REP(i, a.size()) {out<<a[i];if(i!=a.size()-1)out<<',';}
  out<<']';
  return out;
}

int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0};

VI g[150010];
signed main(void)
{
  int n;
  cin >> n;
  REP(i, n-1) {
    int a, b;
    cin >> a >> b;
    a--, b--;
    g[a].PB(b);
    g[b].PB(a);
  }

  vector<double> prob(n);
  function<double(int,int)> dfs1 = [&](int v, int p) -> double {
    if(p != -1 && g[v].size() == 1) return 0;
    int cnt = 0;
    double ret = 0;
    for(int &i: g[v]) {
      if(i == p) continue;
      cnt++;
      ret += dfs1(i, v) + 1;
    }
    ret /= cnt;
    return prob[v] = ret;
  };

  dfs1(0, -1);

  vector<double> ans(n);
  function<void(int,double,int)> dfs2 = [&](int v, double d_par, int p) {
    double ret = 0;
    for(int &e : g[v]) {
      if(e == p) {
        ret += d_par + 1;
      } else {
        ret += prob[e] + 1;
      }
    }
    ans[v] = ret / g[v].size();
    for(int &e : g[v]) {
      if(e == p) continue;
      double nxt = g[v].size() == 1 ?0:(ret-prob[e]-1)/(g[v].size()-1);
      dfs2(e, nxt, v);
    }
  };

  dfs2(0, 0, -1);
  REP(i, n) {
    cout << fixed << setprecision(9) << ans[i] << endl;
  }

  return 0;
}