目次
文書の過去の版を表示しています。
AtCoder Grand Contest 031 A,B,C 問題メモ
A - Colorful Subsequence
問題
- 英小文字からなる文字列 $S$ の部分列であって、全て異なる文字からなるものの個数を $\mod 10^9+7$ で求めよ
- 文字列として同じでも、いずれかの文字が異なる場所から取り出されていれば、異なる部分列として数える
- $1 \le |S| \le 10^5$
解法
「'a'を使うとしたら、どの位置の'a'を使うか」は、$S$ における 'a' の個数通りある。これを $P_a$ とする。これを各文字について求める。
どの文字を使うか(使わないか)のパターン数は、使わない場合の1通りを加えて $(P_a+1)\times(P_b+1)\times ... \times(P_z+1)$ となる。
どの文字を使うか決めれば、部分列の並び方は自動的に $S$ における順番になり一意に決まるので、並び順については考えなくてよい。
よってこのパターン数がそのまま答えとなる。ただし、1文字も選ばない1パターンは除いておく。
from collections import Counter n = int(input()) s = input() cnt = Counter(s) ans = 1 MOD = 10 ** 9 + 7 for c, v in cnt.items(): ans = ans * (v + 1) % MOD print((ans - 1) % MOD)
B - Reversi
問題
- $N$ 個の石が一列に並ぶ
- 石はそれぞれ色 $c_1,c_2,...,c_N$ で塗られている
- 以下の操作を0回以上、任意の回数行える
- 同じ色の2つの石に挟まれた間の石を、挟んだ石の色で塗り替える
- 最終的な石の色の並び方としてありうるものの個数を $\mod{10^9+7}$ で求めよ
- $1 \le N \le 2\times 10^5$
解法
DPで解く。
- $dp[i]=i$ 番目の石までを使った、あり得る並び方の個数
- $dp[0]=1$
今考えている石の色を $c_i$ とした時、1つ前に $c_i$ が出てきた場所を $j$ とする。
DPの更新は、$j~i$ について操作を行う場合と行わない場合を合算する。
- $c_i$ がはじめて出てきたとき
- 操作を行えない
- $dp[i] = dp[i-1]$
- $j+1=i$ の時
- 同じ色が連続していて挟まれた石がないので、操作してもしなくても同じ
- $dp[i] = dp[i-1]$
- それ以外の時
- $dp[i] = dp[i-1] + dp[j]$
問題の条件では、操作自体は直前の同色でなくてもっと前の同色の石との間でも行えるが、求めるのは最終的な色の並びなので、「もっと前の同色~$j$」「$j~i$」の2回操作を行ったと考えても変わらない。「もっと前の同色~$j$」の部分は $dp[j]$ までで考慮されているので、新規に考えるのは $j~i$ 間のみでよい。
実装では、あらかじめ同じ色が連続する部分は事前処理で1箇所にまとめてしまうと場合分けが減って楽。
import sys from collections import defaultdict n = int(input()) stones = [-1] for line in sys.stdin: c = int(line) if stones[-1] == c: continue stones.append(c) dp = 1 prev = defaultdict(lambda: 0) MOD = 10 ** 9 + 7 for s in stones[1:]: dp = (dp + prev[s]) % MOD prev[s] = dp print(dp)
C - Differ by 1 Bit
問題
- 正整数 $N$ が与えられる
- $(0,1,2,3,...,2^N-1)$ の順列(並べ替えた数列)を$(P_0,P_1,...,P_{2^N-1})$ とする
- 以下の条件を満たす順列が構築できるか調べ、可能ならば1つ構築せよ
- $A$ で始まり、$B$ で終わる
- 隣り合う全ての2要素について、2進数表現は1桁だけ異なっている
- $1 \le N \le 17$
解法
$A$ と $B$ で異なっているbitで2分割していく。イメージとしては、解説YouTubeの $N$ 次元超立方体の切断を考えるとわかりやすい。
まず、出来るか出来ないかの問題として、2進数で立っている'1'の数を考えると、「…→奇数→偶数→奇数→偶数→…」となっていくはずなので、$A$ と $B$ の立っている'1'の偶奇は異なっていないといけない。同じなら'NO'。
逆にこれを満たすと行けるのか、この時点ではよく分からないが、出来るとしてとりあえず考えを進める。
順列なので、途中で同じ数字が被らないように、1ビットずつ変化させていかなくてはいけない。
N=3 A=1 B=3 0 1 2 3 4 5 6 7 000 001 010 011 100 101 110 111 1 ? ? ? ? ? ? 3 001 011
1と3を見ると、下2ビット目が異なっている。(複数ある場合、異なっているビットをどれか1つ選ぶ)
全体の中で2ビットが'0'と'1'の数字の数は等しい。 よって、真ん中で2ビット目を1回切り替えれば、それ以外では2ビット目を操作する必要は無くなり、かつ前半で「2ビット目が0の数字全て」、後半で「2ビット目が1の数字全て」を網羅すれば、少なくとも前半と後半の間で数字が被る心配は無くなる。
1 ? ? ? | ? ? ? 3 001 ?0? ?0? ?0? | ?1? ?1? ?1? 011
前半の最後の数字を適当に1つ決める。これまでに分割に用いたビット(2ビット目)以外から適当に1つビット位置を選び、$A$ からそのビットだけ変化させた数とすると被らない。ここでは1ビット目を選んだとする。
1 ? ? 0 | 2 ? ? 3 001 ?0? ?0? 000 | 010 ?1? ?1? 011 ^1ビット目を変化^
すると、左半分を「$A=1,B=0$ とし、2ビット目はいじらない」同様の問題として処理できる。
1と0ではで1ビット目が変化しているので(さっきそう変化させたからだが)、真ん中で1ビット目を変化させることにする。
1 ? | ? 0 | 2 ? ? 3 001 ?01 | ?00 000 | 010 ?1? ?1? 011
すると、前半の最後の項(2番目)は、$A=1$ からまだ分割に用いていない3ビット目を変化させ、5とするとよいことがわかる。
1 5 | 4 0 | 2 ? ? 3 001 101 | 100 000 | 010 ?1? ?1? 011
左四半分を「$A=1,B=5$ とし、1,2ビット目はいじらない」問題とすればよいが、これは既に隣り合っているのでここで終了。
右半分も同様に、「$A=2,B=3$ とし、2ビット目はいじらない」として、分割していくと、構築できる。
1 5 | 4 0 | 2 6 | 7 3 001 101 | 100 000 | 010 110 | 111 011
左半分と右半分で次に分割すべきビットは同じとは限らないため、「既に分割に用いたビット」の管理に注意。 分割済ビット位置をリストで持ち、再帰関数の中でappendしてから子の探索に進み、終了後にpopする。
def split(n, a, b, used, ai, ans, width): if width == 2: ans[ai] = a ans[ai + 1] = b return x = a ^ b y = x & -x # Rightmost Bit at which is different for a and b l = y.bit_length() - 1 used.append(l) i = 0 for i in range(n): if i not in used: break la, lb = a, a ^ (1 << i) ra, rb = lb ^ y, b width >>= 1 split(n, la, lb, used, ai, ans, width) split(n, ra, rb, used, ai + width, ans, width) used.pop() def solve(n, a, b): if bin(a).count('1') % 2 == bin(b).count('1') % 2: print('NO') return used = [] ans = [0] * (1 << n) split(n, a, b, used, 0, ans, 1 << n) print('YES') print(*ans) n, a, b = list(map(int, input().split())) solve(n, a, b)