−目次
AtCoder Beginner Contest 122 A~D問題メモ
A - Double Helix
問題
- 塩基を表す文字 'A','C','G','T' のどれか1文字が与えられるので、対になる塩基を出力せよ
- アデニンはチミンと、シトシンはグアニンと対になる
解法
こういうのがパッと出るとどう書いたらいいか迷った挙げ句、何故か記述量の多い方法を選んじゃう。
1 |
print({'A': 'T', 'T': 'A', 'C': 'G', 'G': 'C'}[input()]) |
こちらの方がスマートか。
1 |
print('ATCG'['TAGC'.index(input())]) |
簡潔に書けるかと思ったら文字と文字コードの変換が発生してあまり簡潔にならなかった。str.maketrans()
1 |
print(chr(str.maketrans('ATCG', 'TAGC')[ord(input())])) |
B - ATCoder
問題
- 文字列 S が与えられる
- S の部分文字列で、'A','C','G','T' のみからなるもので、最長のものの長さを求めよ
- 1≤|S|≤10
- S の部分文字列とは、S から連続する部分を切り出した文字列である
- 飛び飛びで拾ってきた文字列は含まない('ATCODER' から 'AC' などは含まない)
解法
左端と右端を全探索。
1 2 3 4 5 6 7 8 |
s = input()ans = 0for l in range(len(s)): for r in range(l, len(s)): t = s[l:r + 1] if all(c in 'ACGT' for c in t): ans = max(ans, len(t))print(ans) |
もしくは、先頭から1文字ずつ「今までで'A','C','G','T'が連続している個数カウント」を持ちながら見ていく。
ACGTのどれかだったらカウントを1増やし、どれでもなかったら0にリセットする。途中経過を含めたカウントの最大値が答え。
1 2 3 4 5 6 7 8 9 10 |
s = input()ans = 0tmp = 0for c in s: if c in 'ACGT': tmp += 1 ans = max(ans, tmp) else: tmp = 0print(ans) |
C - GeT AC
問題
- 'A','C','G','T' のみからなる文字列 S が与えられる
- 問いが Q 個与えられるので、全てに答えよ
- i 番目の問い:
- li,ri が与えられる
- S の li~ri 文字目(両端含む)の部分文字列に、'AC' は何回現れるか?
- 1≤|S|≤105
- 1≤Q≤105
解法
クエリが一杯あるので、毎回、文字列を切り出してカウントしては間に合わない。
以下を事前計算する。
- calc[i]: 先頭から i 文字目までの部分文字列に、'AC' が現れる個数
S AC_A_CA__ACACC__ ... calc 0111111111223333 ...
'C' の時に1増やすようにする。
こうすると、l~r 文字目を見たとき、calc[r]−calc[l] で、その間の個数が求められる。
i 01234567890123456 S AC_A_CA__ACACC__ ... calc 00111111111223333 ... l=2, r=14 S[l:r] C_A_CA__ACACC calc 1 3 → 3 - 1 = 2 個含まれる
元の文字列 S では 'AC' の一部だった 'C' でも、そこを左端に切り出された場合は数えないことに注意。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
from itertools import accumulaten, q = list(map(int, input().split()))s = input()calc = [0] * nprev = Nonefor i, c in enumerate(s): if prev == 'A' and c == 'C': calc[i] = 1 prev = ccalc = [0] + list(accumulate(calc))ans = []for _ in range(q): l, r = list(map(int, input().split())) ans.append(calc[r] - calc[l])print('\n'.join(map(str, ans))) |
D - We Like AGC
問題
- 整数 N が与えられる
- 次の条件を満たす長さ N の文字列の数を、109+7 で割った余りを求めよ
- 3≤N≤100
- 条件
- 'A','C','G','T' のみからなる
- 'AGC' を部分文字列として含まない
- 隣接する2文字をどこか1箇所だけ入れ替えても、'AGC' を部分文字列として含むような文字列にすることはできない
解法
以下のDPで解く。
- 'A'=0, 'C'=1, 'G'=2, 'T'=3 と対応づける
- dp[h][i][j][k]= 末尾から3文字目が i、2文字目が j、1文字目が k であるような長さ h の文字列の個数
初期状態を考える。最初から3文字目までは作成した状態(h=3)とする。
以下の組み合わせは禁止されているので0通り、他は1通りとなる。
i j k A G C ←そのままアウト A C G ←jとkの入れ替えでアウト G A C ←iとjの入れ替えでアウト
遷移を考える。
'AGC' を含まないなどの制約が無ければ、l を新規に末尾に付け加える文字として、各 i,j,k,l (それぞれ0~3)につき、以下のように更新できる。
- dp[h+1][j][k][l]+=dp[h][i][j][k]
さて、ここでアウトになるようなパターンを探して、それを更新から省く。
i j k l A G C ←そのままアウト A C G ←kとlの入れ替えでアウト G A C ←jとkの入れ替えでアウト A G C ←iとjの入れ替えでアウト A G C ←kとlの入れ替えでアウト
空白の部分は任意の文字が当てはまる。(i,j,k,l) がこれらの条件を満たす場合の遷移は行わないようにする。
h=N まで埋めたら、dp[N] の数字を全て合計した値が答え。
実装の上では、h は1つ前の情報しか必要で無いので、ループ時に直前の状態を引き継ぐようにすると省略できる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
MOD = 10 ** 9 + 7n = int(input())dp = [[[1] * 4 for _ in [0] * 4] for _ in [0] * 4]dp[0][2][1] = 0dp[0][1][2] = 0dp[2][0][1] = 0for _ in range(n - 3): ndp = [[[0] * 4 for _ in [0] * 4] for _ in [0] * 4] for i in range(4): for j in range(4): for k in range(4): for l in range(4): if (i, k, l) == (0, 2, 1) or (i, j, l) == (0, 2, 1): pass else: ndp[j][k][l] += dp[i][j][k] dp = ndp dp[0][2][1] = 0 dp[0][1][2] = 0 dp[2][0][1] = 0 for j in range(4): for k in range(4): for l in range(4): dp[j][k][l] %= MODprint(sum(z for x in dp for y in x for z in y) % MOD) |

