ferinの競プロ帳

競プロについてのメモ

エイシング プログラミング コンテスト 2019 E - Attack to a Tree

問題ページ

解法

木DPをする。dp[v][i][j] = (頂点vを根とする部分木でi回辺を切っていて頂点vの連結成分以外は問題文の条件を満たし、頂点vの連結成分が全てバッテリー(j=0)orそれ以外(j=1)のときの連結成分の頂点の重みの和の最小) とする。不可能な場合はinfをdpに持たせる。頂点vを根とする木とvの子cを根とする木をマージしていくことでdpの遷移を行う。頂点vとcの間の辺を切らない場合と切る場合に分けて考える。

切らない場合は各部分木でi回切断、j回切断している状態をマージするのでi+j回切断したときの情報が得られる。マージするどちらかのdpの値がinfであればそれは不可能な状態なので無視する。
new_dp[v][i+j][0] = dp[v][i][0] + dp[c][i][0]
new_dp[v][i+j][1] = dp[v][i][0] + dp[c][i][1]
new_dp[v][i+j][1] = dp[v][i][1] + dp[c][i][0]
new_dp[v][i+j][1] = dp[v][i][1] + dp[c][i][1]

切る場合はi+j+1回切断したときの情報が得られる。切っているので子cの部分木の頂点の重みは足す必要がない。辺を切ってvと連結でなくなる頂点の連結成分が問題文の条件を満たすときのみ辺を切断する遷移を行えることに注意。
new_dp[v][i+j+1][0] = dp[v][i][0]
new_dp[v][i+j+1][0] = dp[v][i][0]
new_dp[v][i+j+1][1] = dp[v][i][1]
new_dp[v][i+j+1][1] = dp[v][i][1]

以上のマージはO(N^2)でこれを各頂点について行うとO(N^3)に一見思えるが部分木のサイズまでしかループを回さないようにすることでO(N^2)となっている。二乗の木 DP - (iwi) { 反省します - TopCoder部
dp[root][i][0]=infかdp[root][i][1]<0であればi回切断が可能と判定でき最終的な答えが求められる。

#include <bits/stdc++.h>

using namespace std;
using ll = long long;
// #define int ll
using PII = pair<ll, ll>;

#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()

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<typename T> vector<T> make_v(size_t a) { return vector<T>(a); }
template<typename T,typename... Ts>
auto make_v(size_t a,Ts... ts) {
  return vector<decltype(make_v<T>(ts...))>(a,make_v<T>(ts...));
}
template<typename T,typename V> typename enable_if<is_class<T>::value==0>::type
fill_v(T &t, const V &v) { t=v; }
template<typename T,typename V> typename enable_if<is_class<T>::value!=0>::type
fill_v(T &t, const V &v ) { for(auto &e:t) fill_v(e,v); }

template<class S,class T>
ostream &operator <<(ostream& out,const pair<S,T>& a){
  out<<'('<<a.first<<','<<a.second<<')'; return out;
}
template<typename T>
istream& operator >> (istream& is, vector<T>& vec){
  for(T& x: vec) {is >> x;} return is;
}
template<class T>
ostream &operator <<(ostream& out,const vector<T>& a){
  out<<'['; for(T i: a) {out<<i<<',';} out<<']'; return out;
}

int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0}; // DRUL
const int INF = 1<<30;
const ll LLINF = 1LL<<60;
const ll MOD = 1000000007;

using vll = vector<vector<ll>>;
ll a[5010];
vector<ll> g[5010];
vll dfs(ll v, ll p) {
  // dp[v]
  vll ret(1, vector<ll>(2, LLINF));
  if(a[v]>0) ret[0][0] = a[v];
  ret[0][1] = a[v];
  for(auto to: g[v]) if(to != p) {
    // dp[c]
    auto vec = dfs(to, v);

    // new_dp[v]
    vll nret(ret.size() + vec.size(), vector<ll>(2, LLINF));
    REP(i, ret.size()) REP(j, vec.size()) {
      // vからiまでの辺を切断しない
      if(ret[i][0] != LLINF && vec[j][0] != LLINF) {
        chmin(nret[i+j][0], ret[i][0]+vec[j][0]);
      }
      if(ret[i][1] != LLINF && vec[j][1] != LLINF) {
        chmin(nret[i+j][1], ret[i][1]+vec[j][1]);
      }
      if(ret[i][0] != LLINF && vec[j][1] != LLINF) {
        chmin(nret[i+j][1], ret[i][0]+vec[j][1]);
      }
      if(ret[i][1] != LLINF && vec[j][0] != LLINF) {
        chmin(nret[i+j][1], ret[i][1]+vec[j][0]);
      }
      // vからiまでの辺を切断する
      if(ret[i][0] != LLINF && vec[j][0] != LLINF && i+j+1<nret.size()) {
        chmin(nret[i+j+1][0], ret[i][0]);
      }
      if(ret[i][0] != LLINF && vec[j][1] != LLINF && vec[j][1]<0 && i+j+1<nret.size()) {
        chmin(nret[i+j+1][0], ret[i][0]);
      }
      if(ret[i][1] != LLINF && vec[j][0] != LLINF && i+j+1<nret.size()) {
        chmin(nret[i+j+1][1], ret[i][1]);
      }
      if(ret[i][1] != LLINF && vec[j][1] != LLINF && vec[j][1]<0 && i+j+1<nret.size()) {
        chmin(nret[i+j+1][1], ret[i][1]);
      }
    }
    ret = nret;
  }
  return ret;
}

signed main(void) 
{
  cin.tie(0);
  ios::sync_with_stdio(false);

  ll n;
  cin >> n;
  REP(i, n) cin >> a[i];
  REP(i, n-1) {
    ll u, v;
    cin >> u >> v;
    u--, v--;
    g[u].push_back(v);
    g[v].push_back(u);
  }

  ll ret = INF;
  auto ans = dfs(0, -1);
  REP(i, ans.size()) REP(j, 2) {
    if(j==0 && ans[i][j] != LLINF) chmin(ret, i);
    if(j==1 && ans[i][j] < 0) chmin(ret, i);
  }
  cout << ret << endl;

  return 0;
}

TDPC T - フィボナッチ

問題ページ

解法

きたまさ法メモ - よすぽの日記
この記事を自分なりに理解したメモ

問題では1-indexになっているが0-indexで扱うとする。つまり以下の数列Aの第n項を求めろという問題になる。
A[i] = 1 (i<k)
A[i] = A[i-1] + A[i-2] + … + A[i-k] (i>=k)

行列累乗を使えばO(K^3N)で解ける(cf. 蟻本p114)がこれでは当然TLEする。きたまさ法と呼ばれる線形K項間漸化式の第n項をO(K^2logN)で求めるアルゴリズムを用いる。関数f(m)を数列xを返す関数とする。数列xはA[m]=x[0]A[0]+x[1]A[1]+…+x[k-1]A[k-1]を満たすとする。f(n)を求めることができればA[n]も求まる。f(m)からf(m+1)、f(2m)を高速に求めることができればダブリング・繰り返し二乗法の要領で計算することでO(logn)回の計算でf(n)が求まる。

f(m)からf(m+1)は以下の要領でO(k)で求められる。
A[m] = x[0]A[0]+x[1]A[1]+…+x[k-1]A[k-1]
A[m+1] = x[0]A[1]+x[1]A[2]+…+x[k-1]A[k]
= x[0]A[1]+x[1]A[2]+…+x[k-1]*(A[0]+A[1]+…+A[k-1])
= x[k-1]A[0] + (x[0]+x[k-1])A[1] + … + (x[k-2]+x[k-1])A[k-1]

f(m)からf(2m)はO(k^2)で求められる。まずO(k^2)かけて上記と同じ要領でf(m),f(m+1),…,f(m+k-1)を求める。f(m)が返す数列のi番目をf(m,i)と書くことにするとA[2m]は以下のように書ける。
A[2m] = x[0]A[n]+x[1]A[n+1]+…+x[k-1]A[n+k-1]
= x[0]*(f(n,0)A[0] + f(n,1)A[1] + … + f(n,k-1)A[k-1]) + … + x[k-1]*(f(n+k-1,0)A[0] + …)
= (x[0]f(n,0) + x[1]f(n+1,0) + … + x[k-1]f(n+k-1,0))A[0] + (x[1]f(n,1) + …)A[1] + … + (x[k-1]f(n,k-1) + …)A[k-1]

以上のようにf(m)からf(m+1)、f(2m)を計算することがO(k^2)でできるため第n項をO(k^2logn)で求めることができた。

#include <bits/stdc++.h>
 
using namespace std;
using ll = long long;
// #define int ll
using PII = pair<ll, ll>;
 
#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()
 
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<typename T> vector<T> make_v(size_t a) { return vector<T>(a); }
template<typename T,typename... Ts>
auto make_v(size_t a,Ts... ts) { 
  return vector<decltype(make_v<T>(ts...))>(a,make_v<T>(ts...));
}
template<typename T,typename V> typename enable_if<is_class<T>::value==0>::type
fill_v(T &t, const V &v) { t=v; }
template<typename T,typename V> typename enable_if<is_class<T>::value!=0>::type
fill_v(T &t, const V &v ) { for(auto &e:t) fill_v(e,v); }
 
template<class S,class T>
ostream &operator <<(ostream& out,const pair<S,T>& a){
  out<<'('<<a.first<<','<<a.second<<')'; return out;
}
template<typename T>
istream& operator >> (istream& is, vector<T>& vec){
  for(T& x: vec) {is >> x;} return is;
}
template<class T>
ostream &operator <<(ostream& out,const vector<T>& a){
  out<<'['; for(T i: a) {out<<i<<',';} out<<']'; return out;
}
 
int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0}; // DRUL
const int INF = 1<<30;
const ll LLINF = 1LL<<60;
const int MOD = 1000000007;

// a_nをO(K^2logN)で求める
vector<ll> dfs(vector<ll> a, vector<ll> d, ll n) {
  ll k = d.size();
  if(n == k) return d;
  vector<ll> ret(k);
  if(n&1 || n<k*2) {
    auto v = dfs(a, d, n-1);
    ret[0] = v[k-1] * a[0] % MOD;
    FOR(i, 1, k) ret[i] = (v[i-1] + v[k-1] * a[i] % MOD) % MOD;
  } else {
    auto v = dfs(a, d, n/2);
    vector<vector<ll>> f(k, vector<ll>(k));
    f[0] = v;
    FOR(i, 1, k) {
      f[i][0] = f[i-1][k-1] * a[0] % MOD;
      FOR(j, 1, k) f[i][j] = (f[i-1][j-1] + f[i-1][k-1] * a[j] % MOD) % MOD;
    }
    REP(i, k) REP(j, k) (ret[i] += v[j] * f[j][i] % MOD) %= MOD;
  }
  return ret;
}
ll kitamasa(vector<ll> a, vector<ll> d, ll n) {
  auto ret = dfs(a, d, n);
  ll ans = 0;
  REP(i, d.size()) (ans += d[i] * ret[i]) %= MOD;
  return ans;
}

signed main(void) {
  cin.tie(0);
  ios::sync_with_stdio(false);

  ll n, k;
  cin >> k >> n;
  n--;

  if(n < k) {
    cout << 1 << endl;
    return 0;
  }

  vector<ll> d(k, 1);
  cout << kitamasa(d, d, n) << endl;

  return 0;
}

EDPC W - Intervals

問題ページ

解法

dp[i] = (i文字目までを考え、i文字目を'1'にしたときのスコアの最大) としてDPする。dp[i] = (区間[0,i)に内包される区間で得られるスコア, 全部'0'で0以上にはなる) + (i文字目を'1'にしたことで得られるスコア) = max(0, max(dp[j], j<i)) + (i文字目を'1'にしたことで得られるスコア) となる。max(dp[j]) は区間maxなのでセグメント木で高速に求められる。(i文字目を'1'にしたことで得られるスコア)はiが区間の終端となったときに[l,r]にaを加算することで求められ、遅延セグメント木でO(logN)でできる。よって合計でO(NlogN)で求められる。

#include <bits/stdc++.h>
 
using namespace std;
using ll = long long;
#define int ll
using PII = pair<ll, ll>;
 
#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()
 
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<typename T> vector<T> make_v(size_t a) { return vector<T>(a); }
template<typename T,typename... Ts>
auto make_v(size_t a,Ts... ts) { 
  return vector<decltype(make_v<T>(ts...))>(a,make_v<T>(ts...));
}
template<typename T,typename V> typename enable_if<is_class<T>::value==0>::type
fill_v(T &t, const V &v) { t=v; }
template<typename T,typename V> typename enable_if<is_class<T>::value!=0>::type
fill_v(T &t, const V &v ) { for(auto &e:t) fill_v(e,v); }
 
template<class S,class T>
ostream &operator <<(ostream& out,const pair<S,T>& a){
  out<<'('<<a.first<<','<<a.second<<')'; return out;
}
template<typename T>
istream& operator >> (istream& is, vector<T>& vec){
  for(T& x: vec) {is >> x;} return is;
}
template<class T>
ostream &operator <<(ostream& out,const vector<T>& a){
  out<<'['; for(T i: a) {out<<i<<',';} out<<']'; return out;
}
 
int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0}; // DRUL
const int INF = 1<<30;
const ll LLINF = 1LL<<60;
const int MOD = 1000000007;

template <typename T, typename E>
struct lazySegTree {
  using F = function<T(T,T)>;
  using G = function<T(T,E)>;
  using H = function<E(E,E)>;
  using P = function<E(E,int)>;
  F f; G g; H h; P p; T d1; E d0;
  int n;
  vector<T> dat;
  vector<E> lazy;

  lazySegTree(){}
  lazySegTree(int n_, F f_, G g_, H h_, 
    T d1_, E d0_, P p_=[](E a, int b){return a;})
    : f(f_), g(g_), h(h_), p(p_), d1(d1_), d0(d0_) 
  {
    n = 1; while(n < n_) n *= 2;
    dat.assign(n*2-1, d1);
    lazy.assign(n*2-1, d0);
  }
  void build(vector<T> v) {
    REP(i, v.size()) dat[i+n-1] = v[i];
    for(int i=n-2; i>=0; --i) dat[i] = f(dat[i*2+1], dat[i*2+2]);
  }

  // 区間の幅がlenの節点kについて遅延評価
  inline void eval(int len, int k) {
    if(lazy[k] == d0) return;
    if(k*2+1 < n*2-1) {
      lazy[2*k+1] = h(lazy[k*2+1], lazy[k]);
      lazy[2*k+2] = h(lazy[k*2+2], lazy[k]);
    }
    dat[k] = g(dat[k],p(lazy[k],len));
    lazy[k] = d0;
  }
  // [a, b)
  T update(int a, int b, E x, int k, int l, int r) {
    eval(r-l, k);
    if(b <= l || r <= a) return dat[k];
    if(a <= l && r <= b) {
      lazy[k] = h(lazy[k], x);
      return g(dat[k], p(lazy[k],r-l));
    }
    return dat[k] = f(update(a, b, x, 2*k+1, l, (l+r)/2),
                      update(a, b, x, 2*k+2, (l+r)/2, r));
  }
  T update(int a, int b, E x) { return update(a, b, x, 0, 0, n); }
  // [a, b)
  T query(int a, int b, int k, int l, int r) {
    eval(r-l, k);
    if(a <= l && r <= b) return dat[k];
    bool left = !((l+r)/2 <= a || b <= l), right = !(r <= 1 || b <= (l+r)/2);
    if(left&&right) return f(query(a, b, 2*k+1, l, (l+r)/2), query(a, b, 2*k+2, (l+r)/2, r));
    if(left) return query(a, b, 2*k+1, l, (l+r)/2);
    return query(a, b, 2*k+2, (l+r)/2, r);
  }
  T query(int a, int b) { return query(a, b, 0, 0, n); }
  // デバッグ出力
  void debug() {
    cout << "---------------------" << endl;
    int cnt = 0;
    for(int i=1; i<=n; i*=2) {
      REP(j, i) {
        cout << "(" << dat[cnt] << "," << lazy[cnt] << ") "; 
        cnt++;
      }
      cout << endl;
    }
    cout << "---------------------" << endl;
  }
};
/**
* 区間更新区間max d1=d0=INT_MAX f=max(a,b) g=h=(b==INT_MAX?a:b)\n
* 区間加算区間和  d1=d0=0 f=g=h=a+b p=a*b\n
* 区間加算区間min d1=d0=0 f=min(a,b) g=h=a+b\n
* 区間更新区間和  d1=d0=0 f=a+b g=h=(b==0?a:b) p=a*b\n
* 区間xor区間和   d1=d0=0 f=a+b g=(b>=1?b-a:a) h=a^b p=a*b
*/

signed main(void)
{
  cin.tie(0);
  ios::sync_with_stdio(false);

  ll n, m;
  cin >> n >> m;
  vector<vector<PII>> g(n);
  REP(i, m) {
    ll l, r, a;
    cin >> l >> r >> a;
    l--, r--;
    g[r].push_back({l, a});
  }

  auto f = [](ll a, ll b) {return max(a,b);};
  auto h = [](ll a, ll b) {return a+b;};
  lazySegTree<ll, ll> seg(n+2, f, h, h, 0, 0);

  REP(i, n) {
    ll now = 0;
    if(i) chmax(now, seg.query(0, i)); 
    seg.update(i, i+1, now);
    for(auto p: g[i]) {
      seg.update(p.first, i+1, p.second);
    }
  }
  cout << max(0LL, seg.query(0, n+1)) << endl;

  return 0;
}

EDPC Q - Flowers

問題ページ

解法

花を削除するのではなく条件を満たしつつ追加していくと考える。高さが低い花から順番に挿入していくと考える。dp[i] = (高さがiの花を挿入したときの美しさの最大)としてDPをする。dp[i] = max(dp[j], j<iで高さjの花は高さがiの花より左にある) + (高さがiの花の美しさ) となる。maxを愚直に求めているとO(N^2)で当然TLEしてしまうのでセグメント木を用いて区間maxをO(logN)で求め高速化する。高さが低い方から順番に挿入していくことで"高さjの花は高さがiの花より左にある"の条件を気にせずに区間maxで考えられる。

#include <bits/stdc++.h>
 
using namespace std;
using ll = long long;
// #define int ll
using PII = pair<ll, ll>;
 
#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()
 
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<typename T> vector<T> make_v(size_t a) { return vector<T>(a); }
template<typename T,typename... Ts>
auto make_v(size_t a,Ts... ts) { 
  return vector<decltype(make_v<T>(ts...))>(a,make_v<T>(ts...));
}
template<typename T,typename V> typename enable_if<is_class<T>::value==0>::type
fill_v(T &t, const V &v) { t=v; }
template<typename T,typename V> typename enable_if<is_class<T>::value!=0>::type
fill_v(T &t, const V &v ) { for(auto &e:t) fill_v(e,v); }
 
template<class S,class T>
ostream &operator <<(ostream& out,const pair<S,T>& a){
  out<<'('<<a.first<<','<<a.second<<')'; return out;
}
template<typename T>
istream& operator >> (istream& is, vector<T>& vec){
  for(T& x: vec) {is >> x;} return is;
}
template<class T>
ostream &operator <<(ostream& out,const vector<T>& a){
  out<<'['; for(T i: a) {out<<i<<',';} out<<']'; return out;
}
 
int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0}; // DRUL
const int INF = 1<<30;
const ll LLINF = 1LL<<40;
const ll MOD = 1000000007;

/**
* @brief セグメント木
* @details 抽象化した遅延セグメント木\n
* 区間更新区間max d1=d0=INT_MAX f=max(a,b) g=h=(b==INT_MAX?a:b)\n
* 区間加算区間和  d1=d0=0 f=g=h=a+b p=a*b\n
* 区間加算区間min d1=d0=0 f=min(a,b) g=h=a+b\n
* 区間更新区間和  d1=d0=0 f=a+b g=h=(b==0?a:b) p=a*b\n
* 区間xor区間和   d1=d0=0 f=a+b g=(b>=1?b-a:a) h=a^b p=a*b
*/
template <typename T, typename E>
struct lazySegTree {
  using F = function<T(T,T)>;
  using G = function<T(T,E)>;
  using H = function<E(E,E)>;
  using P = function<E(E,int)>;
  F f; G g; H h; P p; T d1; E d0;
  int n;
  vector<T> dat;
  vector<E> lazy;

  lazySegTree(){}
  lazySegTree(int n_, F f_, G g_, H h_, 
    T d1_, E d0_, P p_=[](E a, int b){return a;})
    : f(f_), g(g_), h(h_), p(p_), d1(d1_), d0(d0_) 
  {
    n = 1; while(n < n_) n *= 2;
    dat.assign(n*2-1, d1);
    lazy.assign(n*2-1, d0);
  }
  void build(vector<T> v) {
    REP(i, v.size()) dat[i+n-1] = v[i];
    for(int i=n-2; i>=0; --i) dat[i] = f(dat[i*2+1], dat[i*2+2]);
  }

  // 区間の幅がlenの節点kについて遅延評価
  inline void eval(int len, int k) {
    if(lazy[k] == d0) return;
    if(k*2+1 < n*2-1) {
      lazy[2*k+1] = h(lazy[k*2+1], lazy[k]);
      lazy[2*k+2] = h(lazy[k*2+2], lazy[k]);
    }
    dat[k] = g(dat[k],p(lazy[k],len));
    lazy[k] = d0;
  }
  // [a, b)
  T update(int a, int b, E x, int k, int l, int r) {
    eval(r-l, k);
    if(b <= l || r <= a) return dat[k];
    if(a <= l && r <= b) {
      lazy[k] = h(lazy[k], x);
      return g(dat[k], p(lazy[k],r-l));
    }
    return dat[k] = f(update(a, b, x, 2*k+1, l, (l+r)/2),
                      update(a, b, x, 2*k+2, (l+r)/2, r));
  }
  T update(int a, int b, E x) { return update(a, b, x, 0, 0, n); }
  // [a, b)
  T query(int a, int b, int k, int l, int r) {
    eval(r-l, k);
    if(a <= l && r <= b) return dat[k];
    bool left = !((l+r)/2 <= a || b <= l), right = !(r <= 1 || b <= (l+r)/2);
    if(left&&right) return f(query(a, b, 2*k+1, l, (l+r)/2), query(a, b, 2*k+2, (l+r)/2, r));
    if(left) return query(a, b, 2*k+1, l, (l+r)/2);
    return query(a, b, 2*k+2, (l+r)/2, r);
  }
  T query(int a, int b) { return query(a, b, 0, 0, n); }
  // デバッグ出力
  void debug() {
    cout << "---------------------" << endl;
    int cnt = 0;
    for(int i=1; i<=n; i*=2) {
      REP(j, i) {
        cout << "(" << dat[cnt] << "," << lazy[cnt] << ") "; 
        cnt++;
      }
      cout << endl;
    }
    cout << "---------------------" << endl;
  }
};

signed main(void)
{
  cin.tie(0);
  ios::sync_with_stdio(false);

  ll n;
  cin >> n;
  vector<ll> h(n), a(n);
  REP(i, n) cin >> h[i];
  REP(i, n) cin >> a[i];

  vector<ll> idx(n);
  iota(ALL(idx), 0);
  sort(ALL(idx), [&](ll l, ll r){
    return h[l] < h[r]; 
  });

  auto f = [](ll a, ll b) { return max(a,b); };
  auto g = [](ll a, ll b) { return (b==0?a:b); };
  lazySegTree<ll,ll> seg(n, f, g, g, 0, 0);  

  vector<ll> dp(n);
  REP(i, n) {
    // 高さiの花を追加するか?
    // [0,idx[i])でdpの値が最大のもの
    dp[i] = (idx[i]==0?0:seg.query(0, idx[i])) + a[idx[i]];
    seg.update(idx[i], idx[i]+1, dp[i]);
  }

  ll ans = 0;
  REP(i, n) chmax(ans, dp[i]);
  cout << ans << endl;

  return 0;
}

EDPC J - Sushi

問題ページ

解法

寿司がa[i]個乗っている皿がどこにあったとしても選ばれる確率に影響はない。したがってN要素の数列a[i]ではなくcnt[i]=(i個乗っている皿の数)と情報を持つことができる。
dfs(x, y, z) = (1個乗っている皿がx枚、2個乗っている皿がy枚、3個乗っている皿がz枚で全ての寿司が無くなる回数の期待値) としてメモ化再帰をする。1個乗っている皿が選ばれる確率がx/n、2個乗っている皿が選ばれる確率がy/n、3個乗っている皿が選ばれる確率がz/n、寿司が乗っていない皿が選ばれる確率が(n-x-y-z)/nである。寿司が乗っていない皿が選ばれた場合にdfs(x,y,z)と素直にdfs関数を呼び出すと無限ループになってしまう。これを回避するため遷移の式以下のように変形する。
dfs(x, y, z) = dfs(x-1, y, z) * x/n + dfs(x+1, y-1, z) * y/n + dfs(x, y+1, z-1) * z/n + dfs(x, y, z) * (n-x-y-z)/n + 1
(1-(n-x-y-z)/n) * dfs(x, y, z) = dfs(x-1, y, z) * x/n + dfs(x+1, y-1, z) * y/n + dfs(x, y+1, z-1) * z/n + 1
dfs(x, y, z) = dfs(x-1, y, z) * x/(x+y+z) + dfs(x+1, y-1, z) * y/(x+y+z) + dfs(x, y+1, z-1) * z/(x+y+z) + n/(x+y+z)
状態数がO(N^3)、遷移がO(1)なのでこの問題は解けた。

#include <bits/stdc++.h>
 
using namespace std;
using ll = long long;
// #define int ll
using PII = pair<ll, ll>;
 
#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()
 
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<typename T> vector<T> make_v(size_t a) { return vector<T>(a); }
template<typename T,typename... Ts>
auto make_v(size_t a,Ts... ts) { 
  return vector<decltype(make_v<T>(ts...))>(a,make_v<T>(ts...));
}
template<typename T,typename V> typename enable_if<is_class<T>::value==0>::type
fill_v(T &t, const V &v) { t=v; }
template<typename T,typename V> typename enable_if<is_class<T>::value!=0>::type
fill_v(T &t, const V &v ) { for(auto &e:t) fill_v(e,v); }
 
template<class S,class T>
ostream &operator <<(ostream& out,const pair<S,T>& a){
  out<<'('<<a.first<<','<<a.second<<')'; return out;
}
template<typename T>
istream& operator >> (istream& is, vector<T>& vec){
  for(T& x: vec) {is >> x;} return is;
}
template<class T>
ostream &operator <<(ostream& out,const vector<T>& a){
  out<<'['; for(T i: a) {out<<i<<',';} out<<']'; return out;
}
 
int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0}; // DRUL
const int INF = 1<<30;
const ll LLINF = 1LL<<60;
const int MOD = 1000000007;

const double EPS = 1e-8;
bool used[305][305][305];
double dp[305][305][305];
ll n, cnt[4];

double dfs(ll x, ll y, ll z) {
  if(!x && !y && !z) return 0;
  if(used[x][y][z]) return dp[x][y][z];
  double ret = (double)n/(x+y+z);
  if(x) ret += dfs(x-1, y, z) * (double)x / (x+y+z);
  if(y) ret += dfs(x+1, y-1, z) * (double)y / (x+y+z);
  if(z) ret += dfs(x, y+1, z-1) * (double)z / (x+y+z);
  used[x][y][z] = true;
  return dp[x][y][z] = ret;
}

signed main(void)
{
  cin.tie(0);
  ios::sync_with_stdio(false);

  cin >> n;
  REP(i, n) {
    ll a;
    cin >> a;
    cnt[a]++;
  }

  cout << fixed << setprecision(15) << dfs(cnt[1], cnt[2], cnt[3]) << endl;

  return 0;
}

AGC030 D - Inversion Sum

問題ページ

考えたこと

  • 全パターンについて数え上げなので確率で考える
  • ペア(A[i], A[j])について反転している確率を求めてこれを足して2^Qを掛ければよさそう
  • A[i]>A[j],i<jとなるjの個数の期待値 = 操作の結果A[i]がj番目に行く確率 * j番目以降にA[i]未満の数が来る個数の期待値
  • p[i][j] = A[i]がj番目に行く確率 として計算する
  • 操作としてx,yが与えられたときp[i][x] = p[i][y] = p[i][x]/2 + p[i][y]/2 となる
  • この処理を各操作について愚直に計算したとしてもO(NQ)で計算できる
  • p[i][j] からj番目以降にA[i]未満の数が来る個数の期待値を求めたい
  • A[i]をソートしておいてA[i]未満の値がj番目に来る確率を持っておきつつ処理みたいなことをしたい
  • B[j] = (j番目にA[i]未満の値が来る確率) をBITを使って持っておくみたいなことを考えた
  • ただしA[i]がj番目に行くと確定させるとB[j]の値が変動してしまうのでまともに値を保持しておけない
    -----解説を見た-----
  • A[i]がj番目に行く確率ではなく(i,j)でA[i]>A[j]となる確率をそのまま持つ
  • p[i][j] = (i,j)でA[i]>A[j]となる確率 とする
  • 操作としてx.yが与えられたときどのように遷移するかを考える
    • p[x][y] = p[y][x] = (p[x][y]+p[y][x])/2 (交換しない場合p[x][y]=p[x][y],交換する場合p[x][y]=p[y][x]で確率は1/2)
    • p[x][i] = p[y][i] = (p[x][i] + p[y][i])/2 (i!=x && i!=y)
    • p[i][x] = p[i][y] = (p[i][x] + p[i][y])/2 (i!=x && i!=y)
  • 以上の遷移はO(N)で処理できるのでO(NQ)で配列pを求めることができる
  • 答えは sum_{i<j} (p[i][j]) * 2^Q となる

値としてp[i][j] = A[i]<A[j]となる確率 みたいな持ち方しか思いつかなかった
交換したあとの位置で考えた方がうまくいく
答えに直結するものをもつ

#include <bits/stdc++.h>
 
using namespace std;
using ll = long long;
// #define int ll
using PII = pair<ll, ll>;
 
#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()
 
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<typename T> vector<T> make_v(size_t a) { return vector<T>(a); }
template<typename T,typename... Ts>
auto make_v(size_t a,Ts... ts) { 
  return vector<decltype(make_v<T>(ts...))>(a,make_v<T>(ts...));
}
template<typename T,typename V> typename enable_if<is_class<T>::value==0>::type
fill_v(T &t, const V &v) { t=v; }
template<typename T,typename V> typename enable_if<is_class<T>::value!=0>::type
fill_v(T &t, const V &v ) { for(auto &e:t) fill_v(e,v); }
 
template<class S,class T>
ostream &operator <<(ostream& out,const pair<S,T>& a){
  out<<'('<<a.first<<','<<a.second<<')'; return out;
}
template<typename T>
istream& operator >> (istream& is, vector<T>& vec){
  for(T& x: vec) {is >> x;} return is;
}
template<class T>
ostream &operator <<(ostream& out,const vector<T>& a){
  out<<'['; for(T i: a) {out<<i<<',';} out<<']'; return out;
}
 
int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0}; // DRUL
const int INF = 1<<30;
const ll LLINF = 1LL<<60;
const int MOD = 1000000007;

signed main(void)
{
  cin.tie(0);
  ios::sync_with_stdio(false);

  ll n, q;
  cin >> n >> q;
  vector<ll> a(n), x(q), y(q);
  REP(i, n) cin >> a[i];
  REP(i, q) cin >> x[i] >> y[i], x[i]--, y[i]--;

  vector<vector<ll>> p(n, vector<ll>(n));
  REP(i, n) REP(j, n) p[i][j] = a[i] > a[j];

  ll inv2 = (MOD+1) / 2;
  REP(i, q) {
    REP(j, n) {
      if(j == x[i] || j == y[i]) continue;
      ll tmp = (p[j][x[i]] + p[j][y[i]]) * inv2 % MOD;
      p[j][x[i]] = p[j][y[i]] = tmp;
      tmp = (p[x[i]][j] + p[y[i]][j]) * inv2 % MOD;
      p[x[i]][j] = p[y[i]][j] = tmp;
    }
    ll tmp = (p[y[i]][x[i]] + p[x[i]][y[i]]) * inv2 % MOD;
    p[x[i]][y[i]] = p[y[i]][x[i]] = tmp;
  }

  ll ans = 0;
  REP(i, n) FOR(j, i+1, n) (ans += p[i][j]) %= MOD;
  REP(i, q) (ans *= 2) %= MOD;
  cout << ans << endl;

  return 0;
}

AGC030 B - Tree Burning

問題ページ

考えたこと

  • まず部分点を取りに行く
  • 木を燃やすときにi番目が燃えているがi-1番目を燃やさずに飛ばすような方法はない
  • 状態がN^2個で収まりそう
  • dp[反時計回りにi個燃やした][時計回りにj個燃やした] = (かかる最大の時間) みたいなDPをしたい
  • 遷移を考えると最後にいた頂点がiとjのどちらなのかわからないと移動にかかる時間がわからない
  • dp[i][j][k(=iとjのどちらにいるかを0/1で表す)] とする
  • dpの初期状態は dp[1][0][0] = x[0], dp[0][1][1] = l-x[n-1] からスタートする
  • このdpの遷移は
    • chmin(dp[i+1][j][0], dp[i][j][0] + x[i] - x[i-1]) (iからi+1に反時計回りに移動)
    • chmin(dp[i+1][j][0], dp[i][j][1] + x[i]+l-x[n-j]) (jからi+1に反時計回りに移動)
    • chmin(dp[i][j+1][1], dp[i][j][1] + x[n-j] - x[n-j-1]) (jからj+1に時計回りに移動)
    • chmin(dp[i][j+1][1], dp[i][j][1] + x[i-1] + l - x[n-j-1]) (iからj+1に時計回りに移動)
  • dpの添字は1-index、木の座標は0-indexで持っていることに注意
  • かなり遷移がややこしくて30分かけてしまったが部分点が通る
  • 満点解法について考える
  • 反時計→時計→反時計→…と交互に切り替えていくような移動をするのが強そう
  • これを使ってDPではなく貪欲をするのが第一感
  • 常に交互に切り替えていくのはサンプル1がすでに反例
  • 実験していると一旦交互に切り替える動作に入った後に同じ向きに進む動作に戻ることはなさそう
  • 証明を考えていると反例が見つかった気がしたのでこの方針をやめる(が、実はこれが想定解法)
  • 向きを切り替える前にどこまで進むかを貪欲に決められるみたいなのを考えるけどだめ
  • 円環だと考えにくいのでどこか1箇所で切って直線にすることを考える
  • どこかの区間[x[i], x[i+1]]は1回も通らないので直線として考えて問題ないはず
  • 直線にして実験していると最適な進み方は高々2通りしかないような気がしてくる
  • 切る位置O(N)通りに対してO(N)かけていたらTLEなので無理
  • 差分を持ってがんばって計算していくのをがんばって考えていたが実装が詰めきれない
    -----解説を見た-----
  • 一度移動する向きを交互に切り替え始めたら同じ方向に移動することはない
  • つまり反時計→時計→時計、時計→反時計→反時計という移動が最適になることはない
  • 証明はここが詳しい AGC 030 B 公式解説の証明 - プログラミング初心者がRubyでAtcoderのBeginnerContestを解いていく
  • 以上の移動パターンを含まないような移動方法は以下の2パターン
    • 反時計→反時計→…→反時計→時計→反時計→時計→…
    • 時計→時計→…→時計→反時計→時計→反時計→…
  • 反時計、時計で移動し続けるときにどこの木まで移動するかを決めうち、残りの交互移動にかかる時間を高速に求められれば解ける
  • 木iまで反時計で移動したあとに交互移動にかかる時間を求める
  • i→n-1→i+1→n-2→i+2→n-3→… と移動していくことがわかる
  • かかる時間は (x[i] + x[n-1]) + (x[n-1] + x[i+1]) + … = x[i] + x[i+1] + x[i+1] + x[i+2] + x[i+2] + … + x[n-1] + x[n-1] + x[n-2] + x[n-2] + … となる
  • 区間和なので累積和等で高速に計算できる形をしている
  • よってこれは解ける
  • 実装上のテクとして反時計回りで移動し続ける場合の答えを計算する部分さえ書いてしまえば時計回りで移動し続ける場合の答えは入力のX[i]を反転し反時計回りで移動し続ける場合と同様に計算することで求められる

証明のときにぼんやりとした命題で考えていたが反時計→時計→時計、時計→反時計→反時計を含むことがないとちゃんとはっきりとした命題に言い換えて考えるべきだった。

#include <bits/stdc++.h>
 
using namespace std;
using ll = long long;
// #define int ll
using PII = pair<ll, ll>;
 
#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()
 
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<typename T> vector<T> make_v(size_t a) { return vector<T>(a); }
template<typename T,typename... Ts>
auto make_v(size_t a,Ts... ts) { 
  return vector<decltype(make_v<T>(ts...))>(a,make_v<T>(ts...));
}
template<typename T,typename V> typename enable_if<is_class<T>::value==0>::type
fill_v(T &t, const V &v) { t=v; }
template<typename T,typename V> typename enable_if<is_class<T>::value!=0>::type
fill_v(T &t, const V &v ) { for(auto &e:t) fill_v(e,v); }
 
template<class S,class T>
ostream &operator <<(ostream& out,const pair<S,T>& a){
  out<<'('<<a.first<<','<<a.second<<')'; return out;
}
template<typename T>
istream& operator >> (istream& is, vector<T>& vec){
  for(T& x: vec) {is >> x;} return is;
}
template<class T>
ostream &operator <<(ostream& out,const vector<T>& a){
  out<<'['; for(T i: a) {out<<i<<',';} out<<']'; return out;
}
 
int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0}; // DRUL
const int INF = 1<<30;
const ll LLINF = 1LL<<60;
const int MOD = 1000000007;

signed main(void)
{
  cin.tie(0);
  ios::sync_with_stdio(false);

  ll l, n;
  cin >> l >> n;
  vector<ll> x(n);
  REP(i, n) cin >> x[i];

  if(n == 1) {
    cout << max(x[0], l-x[0]) << endl;
    return 0;
  }
  
  ll ans = 0;
  auto solve = [&](){
    vector<ll> a(x);
    FOR(i, 1, n) a[i] += a[i-1];
    vector<ll> b(x);
    REP(i, n) b[i] = l-b[i];
    for(ll i=n-2; i>=0; --i) b[i] += b[i+1];
    
    // 木iまで反時計回りで進んだ後交互に往復する
    REP(i, n) {
      ll dist = x[i];
      if(n-i > 1) {
        // [i, i+(n-i-1)/2]
        dist += (a[i+(n-i-1)/2] - (i==0?0:a[i-1])) * 2;
        if((n-i)%2) dist -= x[i+(n-i-1)/2];
        dist -= x[i];
        // [n-(n-i)/2, n-1]
        dist += b[n-(n-i)/2] * 2;
        if((n-i)%2 == 0) dist -= l - x[n-(n-i)/2];
      }
      chmax(ans, dist);
    }
  };

  solve();
  reverse(ALL(x));
  REP(i, n) x[i] = l-x[i];
  solve();

  cout << ans << endl;

  return 0;
}