AtCoder Regular Contest 081E: Don't Be a Subsequence

https://beta.atcoder.jp/contests/arc081

Typical DP Contest FGHIJ - omochan's diary
これのGとほとんど同じで、dp復元がくっついただけ。
でも1-originで逆から見ていくのでかなりこんがらがった。普通に0-originのstringを参照するときに-1をつければいいだけなんだけど。
dpも何を求めているか明確にしないまま実装入ったのもよくない。
あと写経ミスでnexを初期化する部分を間違ってしまった。これコンテストでやったらヤバイ。

int N; ll K;
int nex[MAX_N][26];
ll dp[MAX_N];
int pv[MAX_N];
int E[26];
string str;

void solve() {
	cin >> str; N = (int)str.size();
	rep(i, 0, 26) str += char('a' + i);
	fill(E, E + 26, -1);
	rer(i, N + 27, 0) { //1-origin
		rep(j, 0, 26) nex[i][j] = E[j];
		if(i) E[str[i - 1] - 'a'] = i;
	}
	fill(dp, dp + N + 1, inf);
	fill(pv, pv + N + 1, 1);
	rer(i, N + 1, 0) {
		rep(j, 0, 26) {
			assert(nex[i][j] != -1);
			pi p = pi(dp[nex[i][j]] + 1, str[nex[i][j] - 1] - 'a');
			if(pi(dp[i], str[pv[i] - 1] - 'a') > p) {
				dp[i] = dp[nex[i][j]] + 1;
				pv[i] = nex[i][j];
			}
		}
	}
	int at = 0;
	while(at < N + 1) {
		at = pv[at];
		cout << str[at - 1];
	}
	cout << "\n";
}

Typical Dp Contest PQRST

http://tdpc.contest.atcoder.jp/

PQRSは結構難しい(はず)。解いたやつから順番にあげていきます。
P
木dpするだけのO(NK^2)、と言ってしまえばおしまいだが木dpはバグらせがち。
最初点を主役にして、dpの遷移が意味不明になった。
dp[v][K]:=((vのsubtreeに含まれる辺)+(vとvの親を結ぶ辺)がK本のpathを持つとき)と定義すると解きやすい。
木は点と辺どっちを主役にするかが大事ってそれ一番言われているから。
あとdpテーブルを二つ持つやり方よりも実装が楽な方法がありそうだと思った。

int N, K;
ll dp[1010][60][2];
ll dp2[1010][60][3];
vector<int> G[MAX_N];

void loop(int v, int p) {
	int n = (int)G[v].size();
	rep(i, 0, n) {
		int to = G[v][i];
		if(p == to) continue;
		loop(to, v);
	}
	dp2[0][0][0] = 1;
	rep(i, 0, n) {
		int to = G[v][i];
		if(p == to) {
			rep(j, 0, K + 2) 
				rep(k, 0, 3) dp2[i + 1][j][k] = dp2[i][j][k];
		}
		else {
			rep(j, 0, K + 2)
				rep(k, 0, 3) dp2[i + 1][j][k] = 0;

			rep(j, 0, K + 2) {
				rep(k, 0, 3) {
					for(int l = 0; l + j <= K + 1; l++) {
						ADD(dp2[i + 1][l + j][k], dp2[i][j][k] * dp[to][l][0] % mod);
						if(k != 2) {
							ADD(dp2[i + 1][l + j][k + 1], dp2[i][j][k] * dp[to][l][1] % mod);
						}
					}
				}
			}
		}
	}
	rep(j, 0, K + 2) {
		if(j != 0) ADD(dp[v][j - 1][0], dp2[n][j][2]);
		ADD(dp[v][j][0], dp2[n][j][1]);
		ADD(dp[v][j][0], dp2[n][j][0]);
		ADD(dp[v][j][1], dp2[n][j][1]);
		ADD(dp[v][j + 1][1], dp2[n][j][0]);
	}
}

void solve() {
	cin >> N >> K;
	rep(i, 0, N - 1) {
		int a, b;
		cin >> a >> b; a--; b--;
		G[a].pb(b);
		G[b].pb(a);
	}
	loop(0, -1);
	cout << dp[0][K][0] << "\n";
}

Q
R
何も考えずに最小費用流…をすると死ぬ。
sourceから流れず、負のループ内で完結してしまう流れ方が存在してしまうからだ。
最小費用流はsourceから必ず流れてほしいというシチュエーションが多いので、負のループがある時点でほかの解法を考える方がいいかもしれない。今回はSCCしてDAGにしてから最小費用流すれば良い。(最小費用流の方が脳死で楽だが、もちろんDPでもできる)
SCCとMCFをいっぺんに使うとゴツイ感じ(コード長5425Byte)になって好き。

int N;

void solve() {
	cin >> N;
	SCC::init(N);
	rep(i, 0, N) {
		rep(j, 0, N) {
			int a; cin >> a;
			if(!a) continue;
			SCC::add_edge(i, j);
		}
	}
	N = SCC::get();
	int s = 2 * N, t = 2 * N + 1;
	MCF::init(2 * N + 2);
	rep(i, 0, N) {
		rep(j, 0, (int)SCC::nG[i].size()) {
			int v = SCC::nG[i][j];
			MCF::add_edge(i + N, v, inf, 0);
		}
	}
	rep(i, 0, N) {
		MCF::add_edge(i, i + N, 1, -(int)SCC::C[i].size());
		MCF::add_edge(i, i + N, inf, 0);
		MCF::add_edge(s, i, inf, 0);
		MCF::add_edge(i + N, t, inf, 0);
	}
	cout << -MCF::get(s, t, 2) << "\n";
}

S
グラフの接続関係を持ってDPすればいいです。適当に順番つけて状態量を減らしましょう。

int M, N;
map<vi, ll> dp[110];
int used[12];

void loop(const vector<vi>& G, int v, int at) {
	used[v] = at;
	rep(i, 0, sz(G[v])) {
		int n = G[v][i];
		if(used[n] == M) loop(G, n, at);
	}
}

void solve() {
	cin >> M >> N;
	vi tv(M, M);
	tv[0] = 0;
	dp[0][tv] = 1;

	rep(i, 0, N) {
		for(auto& p: dp[i]) {
			rep(bit, 0, (1 << M)) {
				fill(used, used + 2 * M, M);

				vi vec = p.fst;
				ll v = p.sec;

				vector<vi> G(2 * M);
				rep(i, 0, M) {
					rep(j, i + 1, M) {
						if(vec[i] == vec[j]) {
							G[i].pb(j);
							G[j].pb(i);
						}
					}
					if(vec[i] != M && (bit & (1 << i))) {
						G[i].pb(i + M);
						G[i + M].pb(i);
					}
				}
				rep(i, 0, M - 1) {
					if((bit & (1 << i)) && (bit & (1 << (i + 1)))) {
						int a = i + M, b = i + M + 1;
						G[a].pb(b);
						G[b].pb(a);
					}
				}
				rep(i, 0, M) {
					if(vec[i] == 0 && used[i] == M) {
						loop(G, i, 0);
					}
				}
				int at = 1;
				rep(i, 0, M) {
					if(used[i + M] == M && (bit & (1 << i))) {
						loop(G, i + M, at++);
					}
				}
				vi nv = vi(used + M, used + 2 * M);
				ADD(dp[i + 1][nv], v);
			}
		}
	}

	ll ans = 0;
	for(auto& p : dp[N]) {
		vi vec = p.fst;
		ll v = p.sec;
		if(vec[M - 1] == 0) ADD(ans, v);
	}
	cout << ans << "\n";
}

T
kitamasa法やるだけ。O(k^2log(N))。F(n)=b(k-1)*F(k-1)+b(k-2)*F(k-2)...+b(0)*F(0)として係数のbを求めていく。
F(n)→F(2*n)を求める際2stepで計算する。
1.F(n)を使ってF(2*n)をF(0)...F(2k-1)で表す。
2.F(K)...F(2k-1)をF(0)...F(k-1)で表す。
1はFFTそのものなのでO(klogk)で求められるが、2はFFTできずどうしてもO(k^2)かかると思うんだけどどうだろう…

vl inc(const vl& vec) {
	int n = (int)vec.size();
	vl res(n, 0);
	rep(i, 0, n - 1) {
		res[i + 1] = vec[i];
	}
	rep(i, 0, n) ADD(res[i], vec[n - 1]);
	return res;
}

int K, N;
vl D[MAX_N];

vl mod_fib(ll n) {
	if(n < 2 * K) return D[n];
	if(n % 2 == 1) return inc(mod_fib(n - 1));
	else {
		vl vec = mod_fib(n / 2);
		vl tmp(2 * K, 0);
		rep(i, 0, K) {
			rep(j, 0, K) {
				ADD(tmp[i + j], vec[i] * vec[j] % mod);//step1
			}
		}
		vl res(K, 0);
		rep(i, 0, 2 * K) {
			if(i < K) ADD(res[i], tmp[i]);
			else {
				rep(j, 0, K) {
					ADD(res[j], D[i][j] * tmp[i] % mod);//step2
				}
			}
		}
		return res;
	}
}


void solve() {
	cin >> K >> N;
	rep(i, 0, K) {
		D[i] = vl(K, 0);
		D[i][i] = 1;
	}
	D[K] = vl(K, 1);
	rep(i, K + 1, 2 * K) D[i] = inc(D[i - 1]);
	vl fib = mod_fib(N - 1);
	ll res = 0;
	rep(i, 0, K) ADD(res, fib[i]);
	cout << res << "\n";
}

QとSが解ける見込みがないです…
(2/5/2017更新)あとQだけですね。

Typical DP Contest KLMNO

http://tdpc.contest.atcoder.jp/

K
O(NlogN)ということは問題が簡単な構造をしていますね?区間dpなど複雑なものを考えないでください。
segtreeを使ってできます。
std::findはO(logN)と思っていたのでTLE出た時びっくりした。
あと区間を使う順番に気を付けず、WAも出した。
ちなみにstd::countもO(N)で
set.count、set.findはともにO(logN)です。混同しがち。

int N, M;
ll A[MAX_N], B[MAX_N];

ll vx[MAX_N];
vector<int> G[MAX_N];
segtree seg;

int fd(ll a) {
	return lower_bound(vx, vx + M, a) - vx;
}

//cplp is slow!
//find is not O(logN) but O(N)
void solve() {
	cin >> N;
	rep(i, 0, N) {
		ll x, r;
		cin >> x >> r;
		A[i] = x - r;  B[i] = x + r;
		vx[M++] = A[i];
		vx[M++] = B[i];
	}
	sort(vx, vx + M);
	M = unique(vx, vx + M) - vx;
	seg.init(M);
	rep(i, 0, N) {
		int a = fd(A[i]), b = fd(B[i]);
		G[a].push_back(b);
	}
	rep(i, 0, M) sort(all(G[i])); //please sort

	rep(i, 0, M) {
		rep(j, 0, (int)G[i].size()) {
			int a = G[i][j];
			int b = seg.get(a + 1, M).v;
			int c = seg.get(a, a + 1).v;
			if(c < b + 1) {
				seg.update(a, b + 1);
			}
		}
	}
	cout << seg.get(0, M).v << "\n";
}

L
最初猫の順番は自由だと思っていてこんなのNP-Completeだろ!って思っていた。
i番目の猫から左に距離1以内に含まれる猫の最大indexをL[i]とするとLは単調増加。これを生かしてdpできる。
猫の順番が自由の場合NP-Completeであることを示すなら、ほかのNP-Completeの問題(3-SATなど)に帰着させる必要があるができず。

int N;
int A[MAX_N][MAX_N], S[MAX_N][MAX_N];

int dp[MAX_N][MAX_N];

void solve() {
	cin >> N;
	rep(i, 0, N) 
		rep(j, 0, N) {
			cin >> A[i][j];
			dp[i][j] = -inf;
		}
	
	rep(i, 0, N) {
		rep(j, i + 1, N) {
			S[i][j] = S[i][j - 1] + A[i][j];
		}
	}
	dp[0][0] = 0;

	rep(i, 0, N) {
		rep(j, i, N) {
			if(i != N - 1) MAX(dp[i + 1][j], dp[i][j] + S[i + 1][j]);
			if(j != N - 1) MAX(dp[i][j + 1], dp[i][j] + A[i][j + 1]);
			if(i == j) MAX(dp[i + 1][j + 1], dp[i][j]);
		}
	}
	cout << dp[N - 1][N - 1] * 2 << "\n";
}

M
巡回セールスマンしてから行列累乗。O(R^3*2^R+R^3*log(H))みたいになってヤバそうだがTLEが8秒なので余裕。
bitdpはbitのループを一番外に書きましょう。当たり前ですが。WA取りかけた。

int R, N;
ll dp[16][(1 << 16)];
int G[16][16];

void solve() {
	cin >> R >> N;
	rep(i, 0, N) 
		rep(j, 0, N) cin >> G[i][j];

	mat A(N, vl(N, 0));
	rep(n, 0, N) {
		memset(dp, 0, sizeof(dp));
		dp[n][(1 << n)] = 1;
		rep(bit, 0, (1 << N)) { //don't swap these two
			rep(i, 0, N) {  //
				rep(j, 0, N) {
					if(i == j || !G[i][j]) continue;
					if(bit & (1 << j)) continue;
					if(!dp[i][bit]) continue;
					ADD(dp[j][bit | (1 << j)], dp[i][bit]);
				}
				ADD(A[i][n], dp[i][bit]);
			}
		}
	}
	cout << mat_pow(A, R)[0][0] << "\n";
}

N
最初に使う辺を決めてしまえばあとはそこから順番に使っていくだけ。dpテーブルすらいらない。
木dpなどでありがちのmergeする感じで更新していくと実装軽くて良い。

int N;
int A[MAX_N], B[MAX_N];
ll C[MAX_N][MAX_N];
vector<int> G[MAX_N];
 
void C_init(int n) {
	n++;
	rep(i, 0, n) C[i][0] = 1;
	rep(i, 1, n) {
		rep(j, 1, i + 1) {
			C[i][j] = C[i - 1][j] + C[i - 1][j - 1];
			C[i][j] %= mod;
		}
	}
}
 
pl merge(pl a, pl b) {
	return make_pair(a.fst * b.fst % mod 
			* C[a.sec + b.sec][a.sec] % mod, a.sec + b.sec);
}
 
pl dfs(int v, int p) {
	pl res(1, 0);
	rep(i, 0, (int)G[v].size()) {
		int n = G[v][i];
		if(n == p) continue;
		pl d = dfs(n, v); d.sec++;
		res = merge(res, d);
	}
	return res;
}
 
void solve() {
	cin >> N;
	C_init(N + 10);
	rep(i, 0, N - 1) {
		cin >> A[i] >> B[i];
		A[i]--; B[i]--;
		G[A[i]].pb(B[i]);
		G[B[i]].pb(A[i]);
	}
	ll res = 0;
	rep(i, 0, N - 1) {
		pl a = dfs(A[i], B[i]);
		pl b = dfs(B[i], A[i]);
		res += merge(a, b).fst;
		res %= mod;
	}
	cout << res << "\n";
}

なおC(Combination)だが、次のようにしてもさほど実行時間が変わらず、N=10^6などでも対応できるので脳死でこっちを常に使えばいいと思う。
invをmod-2の累乗で求めるやつはかなり遅いのでやめましょう。

ll fac[MAX_N], inv[MAX_N], fiv[MAX_N]; //fiv:inv(fac(i))

void C_init(int n) {
	fac[0] = fac[1] = 1;
	inv[1] = 1;
	fiv[0] = fiv[1] = 1;
	rep(i, 2, n + 1) {
		fac[i] = fac[i - 1] * i % mod;
		inv[i] = mod - inv[mod % i] * (mod / i) % mod;
		fiv[i] = fiv[i - 1] * inv[i] % mod;
	}
}

ll C(int a, int b) { //assume a >= b
	if(a < b || a < 0 || b < 0) return 0;
	return fac[a] * fiv[b] % mod * fiv[a - b] % mod;
}

O
同じ文字が連続している隙間の数を持ってdpを行う。
隙間に高々10個の同じ文字を入れることを考えれば良いので、前処理として1~10の足し算による分解(例えば10=1+4+5など)を全通り求めておく。
後は適当に場合の数の計算をすればできる。
10の足し算による分解方法は42通りなので、計算量は26*26*10*42*42*10<=1.2*10^8とかで抑えられて通ります。

なぜかWAしてどうしてだろうと思ってたら、普段使っているテンプレートが間違っていて戦慄。

vector<vi> F[11];
int D[11][50][11];

void loop(int a, vi vec) {
	F[a].pb(vec);
	int n = (int)vec.size();
	int t = vec[n - 1];
	vec.resize(n + 1); n++;
	int s = accumulate(all(vec), 0);
	for(int i = t; i + s <= 10; i++) {
		vec[n - 1] = i;
		loop(i + s, vec);
	}
}

int A[27];
ll dp[27][300];

void solve() {
	C_init(300);
	vi vec(1, 0);
	rep(i, 1, 11) {
		vec[0] = i;
		loop(i, vec);
	}
	rep(i, 1, 11) {
		rep(j, 0, (int)F[i].size()) {
			rep(k, 0, 11) {
				D[i][j][k] = count(all(F[i][j]), k);
			}
		}
	}
	F[0].pb(vi(1, 0));
	D[0][0][0] = 1;

	dp[0][0] = 1;
	int s = 1;
	rep(i, 0, 26) cin >> A[i];
	rep(n, 0, 26) {
		rep(m, 0, s + 1) {
			rep(i, 0, A[n] + 1) {
				int a = i, b = A[n] - i;
				int as = s - m, bs = m;
				rep(j, 0, (int)F[a].size()) {
					rep(k, 0, (int)F[b].size()) {
						int an = (int)F[a][j].size(), bn = (int)F[b][k].size();
						if(F[a][j][0] == 0) an = 0;
						if(F[b][k][0] == 0) bn = 0;

						if(as < an || bs < bn) continue;
						ll tmp = 1;
						MUL(tmp, fac[as] * fiv[as - an] % mod);
						MUL(tmp, fac[bs] * fiv[bs - bn] % mod);
						rep(l, 0, 11) {
							MUL(tmp, fiv[D[a][j][l]]);
							MUL(tmp, fiv[D[b][k][l]]);
						}
						int to = m;
						rep(l, 0, an) {
							to += F[a][j][l] - 1;
						}
						rep(l, 0, bn) {
							to += F[b][k][l] - 2;
						}
						assert(to >= 0);
						ADD(dp[n + 1][to], dp[n][m] * tmp % mod);
					}
				}
			}
		}
		s += A[n];
	}
	cout << dp[26][0] << "\n";
}

単調性大事なのはわかるけど、dpとの関連性がよくわからない…
あとOは重複組合せすればめんどくさいことしなくて済みますね…

Typical DP Contest FGHIJ

http://tdpc.contest.atcoder.jp/

F
駅に一回も止まらない方法は1or0通りなので
駅に一回は止まる方法を求めれば良い。
区間和を含んだ式になるが、大体形は決まっているので、代わりにsという値で更新を行っている。
あとは場合分けをきちんとする。

int N, K;
ll dp[1000010];

void solve() {
	cin >> N >> K;
	ll s = 0;
	rep(i, 1, N - 1) {
		if(i < K) { //dp[i] = (dp[i - 1] ~ dp[1]) + 1
			dp[i] = (s + 1) % mod;
			ADD(s, dp[i]);
		}
		else if(i == K) { //dp[i] = (dp[i - 1] ~ dp[1])
			dp[i] = s;
			ADD(s, dp[i]);
		}
		else { //dp[i] = (dp[i - 1] ~ dp[i - K])
			dp[i] = s;
			ADD(s, dp[i]);
			s = (s - dp[i - K] + mod) % mod;
		}
	}
	ll res = 0;
	rer(i, N, max(N - K, 1)) {
		ADD(res, dp[i]);
	}
	if(N < K) ADD(res, 1);
	cout << res << "\n";
}

G
まずiから始まる部分文字列dp[i]の数を求める。
str[i]の次に使う文字はa...zとあるが、i番目以降に各文字が現れる最小のindexをnext[i][j](j=a...z)としておくと、
dp[i]=1+Σdp[next[i][j]]となる。
次に復元は先頭の文字を決めていくことで行う。
index=-1から初めてdp[next[index][j]]の値の和を求めておく。
その値がK以上になったjが最初の文字となる。これを繰り返す。

int N; ll K;
int nex[MAX_N][26];
ll dp[MAX_N];
int E[26];
string str;

void solve() {
	cin >> str; N = (int)str.size();
	cin >> K;
	memset(E, -1, sizeof(E));
	rer(i, N + 1, 0) { //1-origin
		rep(j, 0, 26) nex[i][j] = E[j];
		if(i) E[str[i - 1] - 'a'] = i;
	}
	rer(i, N + 1, 0) {
		dp[i] = (i > 0);
		rep(j, 0, 26) {
			if(nex[i][j] == -1) continue;
			dp[i] += dp[nex[i][j]];
			MIN(dp[i], K + 1);
		}
		//debug(i, dp[i]);
	}
	if(dp[0] < K) {
		cout << "Eel\n";
		return;
	}
	int at = 0;
	while(K > 0) {
		ll tmp = 0; int i = 0;
		while(nex[at][i] == -1 || tmp + dp[nex[at][i]] < K) {
			tmp += dp[nex[at][i]];
			i++;
		}
		K -= (tmp + 1);
		at = nex[at][i];
		cout << char('a' + i);
	}
	cout << "\n";
}

H
O(NWC)でいいのか…
配列の更新を少しミスって危なかった。サンプルで引っかかったのは幸い。

int N, W, C;
int dp[2][10010][55][2];
int w[110], v[110], c[110];

void solve() {
	cin >> N >> W >> C;
	rep(i, 0, N) {
		cin >> w[i] >> v[i] >> c[i];
		c[i]--;
	}
	int now = 0, nex = 1;
	rep(i, 0, W + 1) 
		rep(j, 0, C + 1) {
			dp[0][i][j][0] = -inf;
			dp[0][i][j][1] = -inf;
		}

	dp[0][0][0][0] = 0;

	rep(i, 0, 50) {
		rep(j, 0, N) {
			if(c[j] == i) {
				rep(k, 0, W + 1) 
					rep(l, 0, C + 1) {
						dp[nex][k][l][0] = -inf;
						dp[nex][k][l][1] = -inf;
					}

				rep(k, 0, W + 1) {
					rep(l, 0, C + 1) {
						MAX(dp[nex][k][l][0], dp[now][k][l][0]);
						if(k + w[j] <= W)
							MAX(dp[nex][k + w[j]][l + 1][1], dp[now][k][l][0] + v[j]);
						MAX(dp[nex][k][l][1], dp[now][k][l][1]);
						if(k + w[j] <= W)
							MAX(dp[nex][k + w[j]][l][1], dp[now][k][l][1] + v[j]);
					}
				}
				nex = 1 - nex; now = 1 - now;
			}
		}
		rep(k, 0, W + 1)  
			rep(l, 0, C + 1) {
				MAX(dp[now][k][l][0], dp[now][k][l][1]);
				dp[now][k][l][1] = -inf; //don't forget this
			}
	}
	int res = 0;
	rep(k, 0, W + 1)
		rep(l, 0, C + 1) MAX(res, dp[now][k][l][0]);

	cout << res << "\n";
}

I
まず前処理として文字を余さずiwiを取り出せる区間をdpで求める。
この作業を逆から見ると、連続するiwiを取り出すか、端のiを2つともとり、中央のwをとるかの2つの作業があることがわかる。
無駄文字がある場合は前処理のdpを利用して別のdpを解く。

int N;
int dp[310][310];
int dp2[310];

string str;

void solve() {
	cin >> str; N = (int)str.size();
	rep(i, 0, N + 1) 
		rep(j, 0, N + 1) if(i != j) dp[i][j] = -inf;

	rep(k, 1, N / 3 + 1) {
		rep(l, 0, N) {
			int r = l + k * 3;
			if(r > N) continue;
			if(str[l] == 'i' && str[r - 1] == 'i') {
				for(int i = l + 1; i < r; i++) {
					if(str[i] == 'w') {
						MAX(dp[l][r], dp[l + 1][i] + dp[i + 1][r - 1] + 1);
					}
				}
			}
			for(int i = l; i + 3 <= r; i += 3) {
				if(str[i] == 'i' && str[i + 1] == 'w' && str[i + 2] == 'i') {
					MAX(dp[l][r], dp[l][i] + dp[i + 3][r] + 1);
				}
			}
		}
	}
	rep(i, 0, N) {
		for(int j = 3; j <= N; j += 3) {
			MAX(dp2[i + j], dp2[i] + dp[i][i + j]);
		}
		MAX(dp2[i + 1], dp2[i]);
	}
	cout << dp2[N] << "\n";
}

J
期待値の式を立てるだけなんだけど、両辺に同じdpの項が出る場合があるので移項して計算する。

int N;
double dp[(1 << 16)];

void solve() {
	cin >> N;
	int nbit = 0;
	rep(i, 0, N) {
		int a; cin >> a;
		nbit |= (1 << a);
	}
	N = 16;
	rep(bit, 1, (1 << N)) {
		dp[bit] = inf;
		rep(j, 1, N - 1) {
			int cnt = 0; double tmp = 1.0;
			rep(k, -1, 2) {
				if(bit & (1 << (j + k))) {
					tmp += dp[bit ^ (1 << (j + k))] / 3.0;
				}
				else cnt++;
			}
			if(cnt == 3) continue;
			MIN(dp[bit], 3.0 * tmp / (3.0 - cnt));
		}
	}
	cout << dp[nbit] << "\n";
}

Gが難しかった。
どうすれば独立に文字列を数え上げられるかがよくわかっていない。KMPもSAも使えないので精進あるのみです。

Typical DP Contest ABCDE

http://tdpc.contest.atcoder.jp/assignments

Typical DP Contestの問題が最近のCodeforcesでも出たということを聞いて埋めておこうと思った。

20問あるので5問ずつに分けます。

A
配列使いまわしもして十分高速だけど、boolのdpだとどっかで無駄に情報持ってないか心配になる。

int N;
bool dp[10010];
 
void solve() {
	cin >> N;
	dp[0] = true;
	rep(i, 0, N) {
		int a; cin >> a;
		rer(i, 10001, 0) {
			if(i - a >= 0 && dp[i - a]) dp[i] = true;
		}
	}
	int res = 0;
	rep(i, 0, 10001) {
		res += dp[i];
	}
	cout << res << "\n";
}

B
ゲームを後ろから見ればわかる。

int A, B;
int D[1010], E[1010];
int dp[1010][1010];

void solve() {
	cin >> A >> B;
	rep(i, 0, A) cin >> D[i];
	rep(i, 0, B) cin >> E[i];
	reverse(D, D + A); reverse(E, E + B);

	rep(i, 0, A + 1) {
		rep(j, 0, B + 1) {
			if((A + B - i - j) % 2 == 0) dp[i][j] = 0;
			else dp[i][j] = inf;
		}
	}

	dp[0][0] = 0;
	rep(i, 0, A + 1) {
		rep(j, 0, B + 1) {
			bool snuke = ((A + B - i - j) % 2 == 0);
			if(snuke) {
				if(i) MAX(dp[i][j], dp[i - 1][j] + D[i - 1]);
				if(j) MAX(dp[i][j], dp[i][j - 1] + E[j - 1]);
			}
			else {
				if(i) MIN(dp[i][j], dp[i - 1][j]);
				if(j) MIN(dp[i][j], dp[i][j - 1]);
			}
		}
	}
	cout << dp[A][B] << "\n";
}

C
O(K*4^K)なんて10^7くらいだから余裕で通るのに、O(K*2^K)にしようとうんうん唸っていたの謎。計算式そのまま。

int N, K;
double dp[11][1040];
int R[1040];

void solve() {
	cin >> K;
	N = (1 << K);
	rep(i, 0, N) {
		cin >> R[i];
		dp[0][i] = 1.0;
	}

	rep(i, 0, K) {
		rep(j, 0, N) {
			int a = (1 << i);
			int b = j / a;
			if(b % 2 == 0) {
				rep(k, (b + 1) * a, (b + 2) * a) {
					dp[i + 1][j] += dp[i][k] / (1.0 + pow(10.0, (R[k] - R[j]) / 400.0));
				}
			}
			else {
				rep(k, (b - 1) * a, b * a) {
					dp[i + 1][j] += dp[i][k] / (1.0 + pow(10.0, (R[k] - R[j]) / 400.0));
				}
			}
			dp[i + 1][j] *= dp[i][j];
		}
	}
	rep(i, 0, N) cout << dp[K][i] << "\n";
}

D
1~6までならあり得る約数は2,3,5しかないので、こいつらを持つ。

int N, A, B, C;
ll D;
double dp[2][40][40][40];

void solve() {
	cin >> N >> D;
	while(D % 2 == 0) {
		D /= 2;
		A++;
	}
	while(D % 3 == 0) {
		D /= 3;
		B++;
	}
	while(D % 5 == 0) {
		D /= 5;
		C++;
	}
	if(D != 1) {
		cout << 0 << "\n";
		return;
	}
	dp[0][0][0][0] = 1.0;

	int now, nex;
	rep(i, 0, N) {
		now = i % 2, nex = 1 - now;
		rep(j, 0, A + 1) {
			rep(k, 0, B + 1) {
				rep(l, 0, C + 1) dp[nex][j][k][l] = 0.0;
			}
		}
		rep(j, 0, A + 1) {
			rep(k, 0, B + 1) {
				rep(l, 0, C + 1) {
					double s = dp[now][j][k][l] / 6;
					dp[nex][j][k][l] += s;
					dp[nex][min(j + 1, A)][k][l] += s;
					dp[nex][j][min(k + 1, B)][l] += s;
					dp[nex][min(j + 2, A)][k][l] += s;
					dp[nex][j][k][min(l + 1, C)] += s;
					dp[nex][min(j + 1, A)][min(k + 1, B)][l] += s;
				}
			}
		}
	}
	cout << dp[nex][A][B][C] << "\n";
}

E
上の位から見ていってNと一致しているかそうでないか、その時のDで取ったmodを持つ。
Dと一致している場合、modは1通りしかないのに100要素の配列使っているのは無駄ですね…

int D, N;
ll dp[2][100][2];
string str;
 
void solve() {
	cin >> D >> str;
	N = (int)str.size();
	dp[0][0][0] = 1;
 
	int now, nex;
	rep(i, 0, N) {
		int a = str[i] - '0';
		now = i % 2, nex = 1 - now;
		rep(j, 0, D) {
			dp[nex][j][0] = 0;
			dp[nex][j][1] = 0;
		}
		rep(j, 0, D) {
			ADD(dp[nex][(j + a) % D][0], dp[now][j][0]);
			rep(k, 0, a) 
				ADD(dp[nex][(j + k) % D][1], dp[now][j][0]);
			rep(k, 0, 10) 
				ADD(dp[nex][(j + k) % D][1], dp[now][j][1]);
		}
	}
	cout << (dp[nex][0][0] + dp[nex][0][1] - 1 + mod) % mod << "\n";
}

まあここまでは特に問題なし。

Yosupo Wettbewerb E: Oh, Vertexes & Edges!

http://kcs.miz-miz.biz/contest/6/view/E

AtCoder Grand Contest 014E: Blue and Red Tree - omochan's diary

これと全く同じじゃん!と思ったけど微妙に違った。

最初辺に注目してマージテクしようと思ったが(つまりqueueに入れるのを辺にした)、これでは新たに両端が黒になった辺がqueueに入れられることはない。入出力5を見てみるとわかると思う。

なのでqueueに入れるのは新たにmergeされた点で、元のグラフで隣接している点が黒のものがあったらmergeするというのを繰り返す。

queue<int> que;
bool B[MAX_N];

struct UF {
	int par[MAX_N];
	set<int> E[MAX_N]; //edge

	int find(int x) {
		if(par[x] == x) return x;
		else return par[x] = find(par[x]);
	}

	void init(int n) {
		rep(i, 0, n) par[i] = i;
	}

	void unite(int a, int b) { //update and merge
		a = find(a);
		b = find(b);
		if(a == b) return;
		par[a] = b;
		if((int)E[a].size() > (int)E[b].size()) swap(E[a], E[b]);
		for(int w: E[a]) {
			if(E[b].count(w)) que.push(w);
			E[b].insert(w);
		}
		E[a].clear();
	}
};

int N, M;

UF U;
vector<int> E[MAX_N];

void solve() {
	cin >> N;
	string str; cin >> str;
	rep(i, 0, N) {
		if(str[i] == 'B') {
			que.push(i);
		}
	}
	U.init(N);
	cin >> M;
	rep(i, 0, M) {
		int a, b;
		cin >> a >> b; a--; b--;
		E[a].pb(b);
		E[b].pb(a);
		U.E[a].insert(b);
		U.E[b].insert(a);
	}
	while(!que.empty()) {
		int v = que.front(); que.pop();
		if(B[v]) continue;
		B[v] = true;
		for(int w: E[v]) {
			if(B[w]) U.unite(w, v);
		}
	}
	rep(i, 0, N) {
		if(B[i]) cout << "B";
		else cout << "W";
	}
	cout << "\n";
}

テストケース何個か試したけどあってるっぽいから多分あってる(適当)

KMP法

KMPのK - あなたは嘘つきですかと聞かれたら「YES」と答えるブログ

MP法とKMP法の違い - 生きたい

ここを参考にさせてもらいました。

KMPで求めるのはまさに蟻本p327「禁止文字列」における文字列の状態推移である。
すなわち、ある文字列TとSを比較するとして、Sのi番目でマッチングが失敗したとき、次にどこを見るかを線形時間で求めてくれる。
MPよりも高速。

実はMPに少し手を加えるだけでKMPになる。

vector<int> KMP(string S) {
	int n = (int)S.size();
	S += '_';
	vector<int> A(n + 1);
	A[0] = -1;
	int j = -1;
	rep(i, 0, n) {
		while (j >= 0 && S[i] != S[j]) j = A[j];
		j++;
		if(S[i + 1] == S[j]) A[i + 1] = A[j];
		else A[i + 1] = j;
	}
	return A;
}

if(S[i + 1] == S[j]) A[i + 1] = A[j]というのが新しく追加された行だが、S[i + 1]でマッチングが失敗したら、S[i+1]とS[j]は同じ文字なのでS[j]でもマッチングが失敗するからA[j]に移動していいという理屈である。

これによってループ一回あたりO(logK)が達成される。詳しくは上のsnukeさんのブログを参照のこと。

これで何が嬉しいかというと、MP法では「禁止文字列」の下処理がO(K^2)かかっていたが、KMP法ではO(KlogK)となる。
"AACAACAA"みたいな文字列でMP法とKMP法で得られるAを比較してみると、
MP: {-1, 0, 1, 0, 1, 2, 3, 4, 5, 2}
KMP:{-1,-1, 1,-1,-1, 1,-1,-1, 5, 2}
となっており、KMPの方が圧倒的に優秀なことがわかる。

以下はKMP法による「禁止文字列」の下処理

vector<int> A = KMP(S);
rep(i, 0, K) {
	rep(j, 0, 4) {
		if(S[i] == AGCT[j]) nex[i][j] = i + 1;
		else {
			int k = A[i];
			while(k >= 0 && S[k] != AGCT[j]) k = A[k];
			k++;
			nex[i][j] = k;
		}
	}
}

下処理自体はMP法と変わらない。
がんばっても線形にはならない気がするんだけどどうなんだろう…