ferinの競プロ帳

競プロについてのメモ

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;
}

Educational Codeforces Round 47 E. Intercity Travelling

問題ページ

解法

部分集合全通りを求めるのは無理なので見方を変えてi-1km~ikmの区間を難易度a[j]で通過する確率を求めるとしてみる。n=4で試してみると以下の図のようになる。
f:id:ferin_tech:20181226040224p:plain
図を辺ごとに縦に区切るのではなく、難易度a[i]ごとに横で区切ってみる。すると答えはsum(a[i]*(1/2^(i-1))+(n-1)/2^i)*2^(n-1))=sum(a[i]*(n-i+2)/2^i*2^(n-1))=sum(a[i]*(n-i+2)*2^(n-i-1))となる。この式はO(N)で求めることができるので答えを求めることができた。

#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()
#define endl '\n'

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 = 998244353;

struct mint {
  ll x;
  mint(): x(0) { }
  mint(ll y) : x(y>=0 ? y%MOD : y%MOD+MOD) {}
  ll get() const { return x; }
  // e乗
  mint pow(ll e) {
    ll a = 1, p = x;
    while(e > 0) {
      if(e%2 == 0) {p = (p*p) % MOD; e /= 2;}
      else {a = (a*p) % MOD; e--;}
    }
    return mint(a);
  }
  // Comparators
  bool operator <(mint b) { return x < b.x; }
  bool operator >(mint b) { return x > b.x; }
  bool operator<=(mint b) { return x <= b.x; }
  bool operator>=(mint b) { return x >= b.x; }
  bool operator!=(mint b) { return x != b.x; }
  bool operator==(mint b) { return x == b.x; }
  // increment, decrement
  mint operator++() { x++; return *this; }
  mint operator++(signed) { mint t = *this; x++; return t; }
  mint operator--() { x--; return *this; }
  mint operator--(signed) { mint t = *this; x--; return t; }
  // Basic Operations
  mint &operator+=(mint that) {
    x += that.x;
    if(x >= MOD) x -= MOD;
    return *this;
  }
  mint &operator-=(mint that) {
    x -= that.x;
    if(x < 0) x += MOD;
    return *this;
  }
  mint &operator*=(mint that) {
    x = (ll)x * that.x % MOD;
    return *this;
  }
  mint &operator/=(mint that) {
    x = (ll)x * that.pow(MOD-2).x % MOD;
    return *this;
  }
  mint &operator%=(mint that) {
    x = (ll)x % that.x;
    return *this;
  }
  mint operator+(mint that) const { return mint(*this) += that; }
  mint operator-(mint that) const { return mint(*this) -= that; }
  mint operator*(mint that) const { return mint(*this) *= that; }
  mint operator/(mint that) const { return mint(*this) /= that; }
  mint operator%(mint that) const { return mint(*this) %= that; }
};
// Input/Output
ostream &operator<<(ostream& os, mint a) { return os << a.x; }
istream &operator>>(istream& is, mint &a) { return is >> a.x; }

signed main(void) 
{
  cin.tie(0);
  ios::sync_with_stdio(false);
  
  ll n;
  cin >> n;
  vector<mint> a(n);
  REP(i, n) cin >> a[i];

  mint ans = 0;
  FOR(i, 1, n+1) {
    if(i == n) ans += a[i-1] * (n-i+2) * mint(499122177); 
    else ans += a[i-1] * (n-i+2) * mint(2).pow(n-i-1);
  }
  cout << ans << endl;

  return 0;
}

Educational Codeforces Round 48 (Rated for Div. 2) D. Vasya And The Matrix

問題ページ

解法

bitwise xorなのでbitごとに独立に考えられる。したがってbitごとに考えればa,bは0/1の数列に帰着でき、答えの行列も0/1要素しか持たないと考えてよい。1が書かれている行・列には1を奇数個、0が書かれている行・列には1を偶数個置くような行列が存在すればそれが答えになる。
条件を絞ってその条件ならば必ず構成できる or 構成できないことを積み上げていくと全体の条件について証明できるみたいな考え方を使う。まず行と列に1が奇数個存在することを考える。以下の図のように構成すると必ず条件を満たす。
f:id:ferin_tech:20181225230121p:plain
行と列どちらにも1が偶数個存在する場合について考える。以下の図のように構成すればよい。1が0個のパターンには注意が必要だが0の行もしくは列に1を偶数個置くことで構成できる。
f:id:ferin_tech:20181225230146p:plain
最後に行と列でどちらかは1が偶数個、もう片方は奇数個存在する場合について考える。一般性を失わず1の数が行の方が多いと考えることができる。1の行・列のみで構成を作ろうとした場合、1なので奇数個置かなければならない行が存在するが列の制約から1をどこにも置けないという状況になる。0の行、列に1を置いて調整しようとした場合でも偶数個の1しか置けないので残りの奇数個の1の行の分を構成することはできない。
f:id:ferin_tech:20181225230158p:plain
以上のように構成を行うことができる。

実験してある条件で構成できそうな規則を見つけるを繰り返すのがよくあるパターン。xorでbitごとに考える、偶奇性に注目するというのもよく見る。

#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;

vector<vector<ll>> solve(vector<ll> a, vector<ll> b) {
  vector<ll> p, q;
  REP(i, a.size()) if(a[i] == 1) p.push_back(i);
  REP(i, b.size()) if(b[i] == 1) q.push_back(i);

  if(p.size()%2 + q.size()%2 == 1) {
    cout << "NO" << endl;
    exit(0);
  }
  vector<vector<ll>> ret(a.size(), vector<ll>(b.size()));
  if(p.size()%2 + q.size()%2 == 2) {
    REP(i, p.size()) ret[p[i]][q[0]] = 1;
    REP(i, q.size()) ret[p[0]][q[i]] = 1;
  } else {
    if(p.size() == 0) {
      REP(i, q.size()) ret[0][q[i]] = 1;
    } else if(q.size() == 0) {
      REP(i, p.size()) ret[p[i]][0] = 1;
    } else {
      FOR(i, 1, p.size()) ret[p[i]][q[0]] = 1;
      FOR(i, 1, q.size()) ret[p[0]][q[i]] = 1;
    }
  }
  return ret;
}

signed main(void) 
{
  cin.tie(0);
  ios::sync_with_stdio(false);
  
  ll h, w;
  cin >> h >> w;
  vector<ll> a(h), b(w);
  REP(i, h) cin >> a[i];
  REP(i, w) cin >> b[i];

  vector<vector<ll>> ans(h, vector<ll>(w));
  REP(i, 30) {
    vector<ll> aa(h), bb(w);
    REP(j, h) aa[j] = !!(a[j]&1<<i);
    REP(j, w) bb[j] = !!(b[j]&1<<i);

    auto ret = solve(aa, bb);

    REP(j, h) REP(k, w) {
      if(ret[j][k]) ans[j][k] |= 1<<i;
    }
  }

  cout << "YES" << endl;
  REP(i, h) {
    REP(j, w) {
      cout << ans[i][j] << " ";
    }
    cout << endl;
  }

  return 0;
}