オーバーフローで死ぬつらい問題。
問題概要
原文 → TopCoder Statistics - Problem Statement
環状になっているサイズ の数列
がある (0-indexed)。
番目の要素に対して左隣は要素
であり、右隣は要素
である。 (ただし、
番目の要素の右隣は要素
である。)
数列に対して、以下の 2 種類の操作が可能である。
- L: すべての要素に対して、その左隣の要素の値だけ加算する。
- R: すべての要素に対して、その右隣の要素の値だけ加算する。
に対してこの 2 種類の操作を合計 100 回まで行って、数列
を数列
にすることができれば、その操作手順を出力せよ。できないならば、
No solution
を出力せよ。
解説
操作を見ればわかるように、各要素に対して加算の操作しかできないため s[i] > t[i]
となるような要素が一つでもあればその時点で不可能です。また、最初から と
が一致していれば操作する必要がありません。このチェックは最初にすべきでしょう。
今回の場合、総和を比較することによって操作回数を知ることができます。数列 の総和を
として、簡単に説明します。
どちらの操作においても、操作後には現時点での数列の総和ぶん増えます。これは各要素で隣にあるものを足しているので、各要素が 1 度ずつ加算されているということからわかりますね。ですので、操作をするごとに数列の総和は のように増えていくことになります。(
の総和
の総和) に対してこの性質を利用して計算していけば、操作回数が特定できます。
また、L と R の並び順は関係ない こともわかります。これは L -> R と R -> L の結果が等しいことを利用すれば示せます。したがって、LL…L RR…R のような操作手順に帰着できるため、L と R の個数さえ分かれば良いことになります。
答えを導く方法の一つとして DP があります。 dp[i][j][k] := R を i 回、L を j 回行った時の k 番目の要素の値
となるような配列を作ります。この遷移は R を行うか L を行うかしかないので、その 2 つを書けば良いです。
ソースコード
ll dp[110][110][60]; class LR { public: string construct(vector<long long> s, vector<long long> t) { int n = (int)s.size(); rep(i,0,n) if(s[i] > t[i]) return "No solution"; if(s == t) return ""; ll ssum = accumulate(s.begin(), s.end(), (ll)0); ll tsum = accumulate(t.begin(), t.end(), (ll)0); if(ssum == 0 || (tsum - ssum) % ssum) return "No solution"; ll diff = (tsum - ssum) / ssum; ll turn = 1, d = 1; while(1) { if(diff - d < 0) return "No solution"; if(diff - d == 0) break; diff -= d; d *= 2; turn++; } memset(dp, 0, sizeof(dp)); rep(i,0,n) dp[0][0][i] = s[i]; repq(x,1,turn) repq(i,0,x) { int j = x - i; bool ok = true; rep(k,0,n) { ll v = -1, w = -1; if(j != 0) v = dp[i][j-1][k] + dp[i][j-1][(k-1+n)%n]; if(i != 0) w = dp[i-1][j][k] + dp[i-1][j][(k+1) %n]; dp[i][j][k] = max(v, w); if(dp[i][j][k] != t[k]) ok = false; } if(ok) return string(i, 'R') + string(j, 'L'); } return "No solution"; } };
これ通せなかったのつらいなあ (本番は回数の決め打ちをしていなかったのでほげ)