https://atcoder.jp/contests/abc152/tasks/abc152_f
前提知識
解説
https://atcoder.jp/contests/abc152/submissions/9706801
条件の弱い所を探すと、N=50, M=20である。
N=50はまあまあ見るのでいいとして、M=20がどう見ても不自然。
ここから考え出す。
20というと、220が通るなという感じであり、数え上げで、満たすべき制約について…
包除原理である。
包除原理を用いると答えは以下のように求めることができる。
(制約を最低0個満たしていない)-(制約を最低1個満たしていない)+(制約を最低2個満たしていない)-...
これで計算する方針で考えよう。
2Mで制約を満たす満たさないの全パターンを見て、組み合わせを計算し、引くか足すかしていく。
あるmskの集合の制約は満たさないと考えると、その集合に含まれる経路は全部白にする必要がある。
それで、それ以外の部分は何色でもいいので、適当に塗る組み合わせを考える。
これで、集合の個数がcntであるとすると、「制約を最低cnt個満たしていない」組み合わせが得られる。
その集合に含まれる経路に使われる辺の個数をwhite.countとすると、全部でN-1本なので、
rest(=N-1-white.count)本がどっちでも塗れる。
なので、組み合わせは2rest通り。
最後に残ったのは、その集合に含まれる経路の辺の本数をどう数えるかである。
自分は元々ライブラリ化してあるLCAを使って木上を移動するやつを使った。
なくても、前計算できるので、dfsとかで最短経路を見つけて、戻りながら辺を集計すればできそう。
どの辺を使ったかをこちらも集合で記録しておこう。
pre[i] := i番目の制約で使用する辺の集合
これを用意しておくことで、ある集合に含まれる経路の辺集合は、集合のpreのOR和となるので便利。
50なのでllでもいいが、bitsetだとカウントできるからこっちを自分は使った。
色々前計算しないとTLEするので注意。
int N, M; ll po[51]; bitset<50> pre[20]; //--------------------------------------------------------------------------------------------------- void _main() { po[0] = 1; rep(i, 1, 51) po[i] = po[i - 1] * 2; cin >> N; HLDecomposition hld(N); rep(i, 0, N - 1) { int a, b; cin >> a >> b; a--; b--; hld.add(a, b); } hld.build(0); cin >> M; rep(i, 0, M) { int u, v; cin >> u >> v; u--; v--; while (u != v) { int to = hld.go(u, v, 1); if(hld.depth[u] > hld.depth[to]) pre[i].set(u); else pre[i].set(to); u = to; } } ll ans = 0; rep(msk, 0, 1 << M) { int p = __builtin_popcount(msk) % 2; bitset<50> white; rep(i, 0, M) if (msk & (1 << i)) white |= pre[i]; int rest = N - 1 - white.count(); if (p == 0) ans += po[rest]; else ans -= po[rest]; } cout << ans << endl; }