Codeforces #462 Div2C Div1A A Twisty Movement
問題概要
長さnの数列aが与えられる。
数列aの要素は1,2のどちらか。
区間[l,r](l,rは自由)を1度だけ反転する。
反転した後の数列aにおいて最長の非減少数列の長さを求めよ
(非減少数列は に対してを満たすような数列)
解法
この問題は解法が存在する
解法
1. Segment Treeで区間に含まれる2(もしくは1)の個数を管理
2. DPで区間[l,r)に含まれる最長の2....21....1(非連続でもよい)を計算する。この2...21...1は[l,r)をひっくり返すと区間[l,r)における最長の非減少数列になる
3. 各l,rに対して[0,l)に含まれる1の数 + [l,r)の最長の2...21...1列 + [r,N)に含まれる2の数を計算する
この計算を行えば3で求めた値の最大値を出力すればよい
これはだが、SegmentTreeで管理している部分を累積和とかで管理すればになる。
struct RSQ{ public: int n; vector<int> data; RSQ(int n_){ n = 1; while(n<n_) n*=2; data.resize(2*n-1); for(int i=0;i<2*n-1;i++) data[i]=0; } void update(int k,int a){ k+=n-1; data[k]=a; while(k>0){ k=(k-1)/2; data[k]=data[k*2+1]+data[k*2+2]; } } int query(int a,int b,int k,int l,int r){ if(r<=a or b<=l) return 0; if(a<=l and r<=b) return data[k]; else return query(a,b,k*2+1,l,(l+r)/2)+query(a,b,k*2+2,(l+r)/2,r); } int query(int a,int b){ return query(a,b,0,0,n); } }; int main(){ int N; cin >> N; RSQ rsq(N); vector<int> a(N); rep(i,N){ cin >> a[i]; a[i]--; rsq.update(i,a[i]); } vector<vector<int>> dp(N+10,vector<int>(N+10,0)); rep(i,N+1) dp[i][i+1] = 1; for(int len=0;len<=N;len++){ for(int l=0;l<N;l++){ int r=l+len; if(l+1<N and l<N and l+1<=r and r<=N){ if(a[l] == 1) chmax(dp[l][r],dp[l+1][r]+1); else chmax(dp[l][r],dp[l+1][r]); } if(r-1>=0 and r-1<N and l<=r-1){ if(a[r-1] == 0) chmax(dp[l][r],dp[l][r-1]+1); else chmax(dp[l][r],dp[l][r-1]); } } } ll ans = -1; for(int l=0;l<N;l++){ for(int r=l;r<=N;r++){ ll tmp = l-rsq.query(0,l)+dp[l][r]+rsq.query(r,N); ans = max<ll>(ans,tmp); } } cout<< ans << endl; }
解法
数列aを4つの領域に分割する(領域0,領域1,領域2,領域3とする)。
"領域0に含まれる1の個数 + 領域1に含まれる2の個数 + 領域2に含まれる1の個数 + 領域3に含まれる2の個数"を最大化すればよい。
この領域1と領域2の範囲を反転させることで領域0,領域2の反転,領域1の反転,領域3の順になり、領域0,2から1を、領域1,3から2を選択することで最長の1....12....2を作れる。
具体的な実装は
dp[i][j] := a[i]が領域jに含まれる時、a[i]までで、領域0から領域jまでに最大でいくつ"領域に対応した数字(1,2)"が含まれるか
をもったdpをすれば良い。
更新作業は
dp[i][j]に対して、a[i-1]がどの領域に含まれ、a[i]の数字がなにであるかを考えればよいので、
dp[i][j] = max(dp[i-1][j],dp[i-1][j-1]) + (a[i] == "領域に対応する値")
となる
int main(){ int N; cin >> N; vector<int> a(N); rep(i,N) cin >> a[i]; vector<vector<int>> dp(N+10,vector<int>(4,0)); if(a[0]==1){ dp[0][0] = dp[0][2] = 1; dp[0][1] = dp[0][3] = 0; }else if(a[0]==2){ dp[0][0] = dp[0][2] = 0; dp[0][1] = dp[0][3] = 1; } for(int i=1;i<N;i++){ for(int j=0;j<4;j++){ if(j==0 or j==2){ if(a[i]==1){ chmax(dp[i][j],dp[i-1][j]+1); if(j-1>=0) chmax(dp[i][j],dp[i-1][j-1]+1); }else if(a[i]==2){ chmax(dp[i][j],dp[i-1][j]); if(j-1>=0) chmax(dp[i][j],dp[i-1][j-1]); } }else if(j==1 or j==3){ if(a[i]==1){ chmax(dp[i][j],dp[i-1][j]); chmax(dp[i][j],dp[i-1][j-1]); }else if(a[i]==2){ chmax(dp[i][j],dp[i-1][j]+1); chmax(dp[i][j],dp[i-1][j-1]+1); } } } } ll ans = -1; rep(i,4){ chmax(ans,dp[N-1][i]); } cout << ans << endl; }
感想
この問題、Div2Cにしては難しい気がするけどO(N^2)解法なら思いつきたかった....
結構良問だと思うけどどうだろう?
プログラミングコンテストチャレンジブック [第2版] ?問題解決のアルゴリズム活用力とコーディングテクニックを鍛える?
- 作者: 秋葉拓哉,岩田陽一,北川宜稔
- 出版社/メーカー: マイナビ
- 発売日: 2012/01/28
- メディア: 単行本(ソフトカバー)
- 購入: 25人 クリック: 473回
- この商品を含むブログ (35件) を見る
[改訂第3版]C++ポケットリファレンス (POCKET REFERENCE)
- 作者: 高橋晶,安藤敏彦,一戸優介,楠田真矢,湯朝剛介
- 出版社/メーカー: 技術評論社
- 発売日: 2018/02/15
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る
- 作者: 江添亮
- 出版社/メーカー: KADOKAWA
- 発売日: 2018/03/09
- メディア: 単行本
- この商品を含むブログを見る