EDPC V - Subtree

部分木

考え方

ABCでいうと、F問題級。

いわゆる全方位木DPの問題だが、この問題は普通の木DPっぽい思考で解ける。

まず、適当な頂点を根として選び、根付き木にしておく。
この上で黒い頂点全体が連結になるように塗ると、塗った部分もまた根付き木のようになる。
このとき、各頂点について、以下の $2$ つを考える。

すると、その頂点が黒く塗られるパターンは、この積で求められる。

まず、black_root[i] を葉側から求める。

自身が全体の葉だった場合は、$1$。
子がある場合、それぞれの子 j について、白い頂点にするか黒い頂点にするかをわけて考える。
すると、子 j からの寄与は black_root[j] + 1 である。
これを全ての子について掛け合わせれば、black_root[i] が求まる。

そして、black_leaf[j] を根側から求める。
自身が全体の根だった場合は、$1$。
そうではなかった場合の説明を親を i として以下に書く。

i より上側から来る寄与は black_leaf[i] である。
また、子 j 以外の子 x については、それぞれ black_root[x] + 1 通りの選び方がある。
したがって、black_leaf[j] は次のようになる。

black_leaf[j]
= 1 + black_leaf[i] * 子 j 以外の子 x についての (black_root[x]+1) の積

ただし、子を $1$ つ固定するたびに、他の子の積を愚直に取り直すと $O(N^2)$ になって遅い。
そこで、black_root[i] を作っているときに一工夫。
左からの累積積と右からの累積積を求めることで、(black_root[x]+1) の積 部分だけ求めてしまう。
すると、後からちゃんと計算するときに、そこに black_leaf[i] をかけて $+1$ するだけでよくなる。

これで black_root[i]black_leaf[i] が求まったので、各頂点ごと積を取れば答え。

計算量は $O(N)$。

動作例

ここでは、説明用に次の独自入力例で考える。

5 100
1 2
1 3
2 4
2 5

0-indexed に直すと、辺は次のようになる。

0 - 1
0 - 2
1 - 3
1 - 4

頂点 0 を根とする根付き木として見る。

children[0]: {1, 2}
children[1]: {3, 4}
children[2]: {}
children[3]: {}
children[4]: {}

まず、葉側から black_root を求める。

頂点 2, 3, 4 は葉なので、子からの寄与はない。

black_root[2] = 1
black_root[3] = 1
black_root[4] = 1

頂点 1 には子 3, 4 がある。
それぞれの子について、使わない場合と使う場合がある。

black_root[1]
= (black_root[3] + 1) * (black_root[4] + 1)
= (1 + 1) * (1 + 1)
= 4

このとき、後で black_leaf を配るために、子を $1$ つ除いた積も累積積で求めておく。
3, 4 からの寄与は、それぞれ次の値である。

black_root[3] + 1 = 2
black_root[4] + 1 = 2

左からの累積積と右からの累積積を作る。

left:  {1, 2, 4}
right: {4, 2, 1}

これにより、それぞれの子を除いた積が $O(1)$ で求まる。

子 3 以外の積: left[0] * right[1] = 1 * 2 = 2
子 4 以外の積: left[1] * right[2] = 2 * 1 = 2

頂点 0 には子 1, 2 がある。

black_root[0]
= (black_root[1] + 1) * (black_root[2] + 1)
= (4 + 1) * (1 + 1)
= 10

ここでも、子 1, 2 をそれぞれ除いた積を累積積で求めておく。
1, 2 からの寄与は、それぞれ次の値である。

black_root[1] + 1 = 5
black_root[2] + 1 = 2

左からの累積積と右からの累積積は次のようになる。

left:  {1, 5, 10}
right: {10, 2, 1}

したがって、それぞれの子を除いた積は次のようになる。

子 1 以外の積: left[0] * right[1] = 1 * 2 = 2
子 2 以外の積: left[1] * right[2] = 5 * 1 = 5

よって、black_root は次のようになる。

black_root: {10, 4, 1, 1, 1}

次に、根側から black_leaf を求める。

0 には親側がないので、次から始める。

black_leaf[0] = 1

頂点 0 から子 1 に値を渡す。
1 から見た親側には、兄弟である子 2 側の寄与が含まれる。
これは先ほど求めた、子 1 以外の積である。

black_leaf[1]
= 1 + black_leaf[0] * 子 1 以外の積
= 1 + 1 * 2
= 3

頂点 0 から子 2 に値を渡す。
2 から見た親側には、兄弟である子 1 側の寄与が含まれる。
これは先ほど求めた、子 2 以外の積である。

black_leaf[2]
= 1 + black_leaf[0] * 子 2 以外の積
= 1 + 1 * 5
= 6

次に、頂点 1 から子 3 に値を渡す。
3 から見た親側には、頂点 1 より上側の寄与と、兄弟である子 4 側の寄与が含まれる。
これは先ほど求めた、子 3 以外の積である。

black_leaf[3]
= 1 + black_leaf[1] * 子 3 以外の積
= 1 + 3 * 2
= 7

同様に、頂点 1 から子 4 に値を渡す。
ここでも、先ほど求めた子 4 以外の積を使う。

black_leaf[4]
= 1 + black_leaf[1] * 子 4 以外の積
= 1 + 3 * 2
= 7

よって、black_leaf は次のようになる。

black_leaf: {1, 3, 6, 7, 7}

最後に、それぞれの頂点について black_root[i] * black_leaf[i] を求める。

頂点 0: 10 * 1 = 10
頂点 1: 4 * 3 = 12
頂点 2: 1 * 6 = 6
頂点 3: 1 * 7 = 7
頂点 4: 1 * 7 = 7

よって、この入力例に対する出力は次である。

10
12
6
7
7

注意点

答えは m で割った余りを要求されているので、剰余類環の考えに従って処理する。
今回は法が $10^9+7$ 固定ではなく、入力で与えられる m である点に注意する。

別解

根付きにしないまま、各辺に「そこから先の部分木を、根を黒くして塗る方法」を持たせる実装もある。
深さ優先探索的に求めていくことになるが、「自身以外の積を求める」部分がかなり複雑になる。