AtCoder Beginner Contest 122
C - GeT AC
https://atcoder.jp/contests/abc122/tasks/abc122_c
方針
index は 0 始まりとします。
0 文字目から n 文字目までに含まれる 'AC' の数を とすると、 文字目から 文字目までに含まれる 'AC' の数は で表されます。あらかじめ について を求めておくと、各クエリに対して で答えを出すことが出来ます。
ここでクエリに対する答えは であって、 ではないです。これは 文字目が A で 文字目が C である場合を除くためです。
を求めるには、あらかじめ と初期化しておき、 i 文字目と i-1 文字目で 'AC' が作られるときに とします。最後に の累積和を取ればよいです。
ソースコード
#include <bits/stdc++.h> using namespace std; int main(){ int N, Q; string S; cin >> N >> Q >> S; int L = S.length(); vector<int> f(L,0); for(int i=1; i<L; i++){ if(S[i-1]=='A' && S[i]=='C'){ f[i] = 1; } } for(int i=1; i<L; i++) f[i] += f[i-1]; vector<int> l(Q,0), r(Q,0); for(int i=0; i<Q; i++) { cin >> l[i] >> r[i]; l[i]--; r[i]--; } for(int i=0; i<Q; i++){ cout << f[r[i]] - f[l[i]] << endl; } }
D - We Like AGC
https://atcoder.jp/contests/abc122/tasks/abc122_d
方針
以下では 'A', 'G', 'C', 'T' のみから成る文字列のみを考えます。このもとで、
の2条件を満たす文字列の数を数えます。
この条件はより具体的には、文字列の中に以下を含まないことと同じです:
ここで ? はA,G,C,T の中の任意の 1 文字です。よって末尾の 3 文字がわかれば、次に付け加えることの出来る文字が決まります。
(長さ の文字列のうち、末尾 3 文字が左から順に であるようなものの数)
とします。ただし を、それぞれ A, G, C, T に対応させます。例えば、 は、末尾が 'ATC' であるような長さ の文字列の数です。
求めたいものは、 となります。
まず入力例 1 より長さ 3 の場合は "AGC", "ACG", "GAC" のみが禁止されます。よって、
となります(上記以外のすべての dp は、あらかじめ 0 で初期化されているものとします)。
次に がわかっているときの の求め方を考えます。
(1) 末尾 3 文字が "?AC" のとき
の場合に対応します。
このとき次の 1 文字として G 以外を用いることが出来ます。よって、
とすることが出来ます。
ここで状態 (i, j, k) に 1 文字追加するので、次の状態は (j, k, (次の文字)) となります。次の文字は G 以外なので です。
(2) 末尾 3 文字が "?AG", "?GA", "A?G", "AG?" のとき
文字列と は以下のように対応します:
"?AG" ⇔
"?GA" ⇔
"A?G" ⇔
"AG?" ⇔
このとき次の 1 文字として C 以外を用いることが出来ます。よって、
とできます。
(3) 上記以外のとき
このときは次の 1 文字として任意の文字を使えます。よって、
とできます。
以上の関係を用いて、 から まで順に を求めていけばよいです。
ソースコード
#include <bits/stdc++.h> using namespace std; int main(){ int N; cin >> N; int M = 1e9+7; vector<int> dp[4][4][4]; for(int i=0; i<4; i++) for(int j=0; j<4; j++) for(int k=0; k<4; k++){ dp[i][j][k] = vector<int>(N+1, 0); } // 長さ 3 の文字列は AGC, ACG, GAC 以外が許される。 for(int i=0; i<4; i++) for(int j=0; j<4; j++) for(int k=0; k<4; k++){ if(!((i==0 && j==1 &&k==2)||(i==1 && j==0 && k==2)||(i==0 && j==2 && k==1))){ dp[i][j][k][3] = 1; } } for(int n=3; n<N; n++){ for(int i=0; i<4; i++) for(int j=0; j<4; j++) for(int k=0; k<4; k++){ if(j==0 && k==2){ // ?AC の後ろには G 以外が入る dp[j][k][0][n+1] = (dp[j][k][0][n+1] + dp[i][j][k][n])%M; dp[j][k][2][n+1] = (dp[j][k][2][n+1] + dp[i][j][k][n])%M; dp[j][k][3][n+1] = (dp[j][k][3][n+1] + dp[i][j][k][n])%M; }else if((i==0 && k==1) || (i==0 && j==1) || (j==0 && k==1) || (j==1 && k==0)){ // A?G, AG?, ?AG, ?GA の後ろには C 以外が入る dp[j][k][0][n+1] = (dp[j][k][0][n+1] + dp[i][j][k][n])%M; dp[j][k][1][n+1] = (dp[j][k][1][n+1] + dp[i][j][k][n])%M; dp[j][k][3][n+1] = (dp[j][k][3][n+1] + dp[i][j][k][n])%M; }else { // 上記以外は n+1 文字目としてなんでも使える for(int m=0; m<4; m++){ dp[j][k][m][n+1] = (dp[j][k][m][n+1] + dp[i][j][k][n])%M; } } } } int ret = 0; for(int i=0; i<4; i++) for(int j=0; j<4; j++) for(int k=0; k<4; k++){ ret = (ret + dp[i][j][k][N])%M; } cout << ret << endl; }
反省
はじめは "AGC", "GAC", "ACG" を含まない文字列を数えればよいと考え、サンプル 2 (N=4)で詰まりました。この間違いは具体的に N=4 の場合の文字列を書き出すことで気づくことが出来ました。
そうして上記の方針に修正できたのですが、dp テーブルを dp[64][N] と持ってしまったせいで、遷移を考えるときに複雑になり、延々とバグらせてしまいました。扱いやすい実装って大事ですね。