◆CodeForce 461B◆
Appleman and Tree
本期语录:不求甚解,如同从叶节点倒回到根节点,只知道解决一道题的路径;回归问题的本质,再加以分类,才能清晰地从根节点找到每一个叶节点,从而解析这一类算法的每一个类型。
□谈一谈感想□
现学先写,我刚听完某两位 dalao 对本题的讲解我就开始写 Blog 了……其实是因为我怕我过久了忘掉(T^T)。因为两位 dalao 边讲边在小声地互相讨论,弄得我一脸懵逼,这篇 Blog 里面可能有一些东西的描述有些问题,感想大家指出!
听完题解过后老师笑呵呵地对我们说这是B题(Oh!Terrible!),我还是不要去 CodeForce 的比赛了 ╮(╯﹏╰)╭
□题目□
Description
Appleman has a tree with n vertices. Some of the vertices (at least one) are colored black and other vertices are colored white.
Consider a set consisting of k (0 ≤ k < n) edges of Appleman’s tree. If Appleman deletes these edges from the tree, then it will split into (k + 1) parts. Note, that each part will be a tree with colored vertices.
Now Appleman wonders, what is the number of sets splitting the tree in such a way that each resulting part will have exactly one black vertex? Find this number modulo 1000000007 (109 + 7).
Input
The first line contains an integer n (2 ≤ n ≤ 105) — the number of tree vertices.
The second line contains the description of the tree: n - 1 integers p0, p1, …, pn - 2 (0 ≤ pi ≤ i). Where pi means that there is an edge connecting vertex (i + 1) of the tree and vertex pi. Consider tree vertices are numbered from 0 to n - 1.
The third line contains the description of the colors of the vertices: n integers x0, x1, …, xn - 1 (xi is either 0 or 1). If xi is equal to 1, vertex i is colored black. Otherwise, vertex i is colored white.
Output
Output a single integer — the number of ways to split the tree modulo 1000000007 (109 + 7).
Example
Input 1
3
0 0
0 1 1
Output 1
2
Input 2
6
0 1 1 0 4
1 1 0 0 1 0
Output 2
1
Input 3
10
0 1 2 1 4 4 4 0 8
0 0 0 1 0 1 1 0 0 1
Output 3
27
□大致翻译□
描述
zzh有一棵n个顶点的树。每个顶点要么被涂成了黑色,要么被涂成了白色。
我们考虑删掉k条边,那么这棵树就被分成(k +1)部分。同时,要求,每个部分是都存在黑色的点。
现在,聪明的zzh想要知道,有多少种不同的分割方法,使得分割出来的每个部分恰好只有一个黑点。由于答案可能很大,请对(10^9+7)取模。
输入
第一行包含一个整数n(2≤n≤10^ 5),表示树的顶点数量。
第二行包含了树的描述,有个n-1个非负整数P0,P1,…,Pn-2(0≤PI≤i)。其中pi表示第(i + 1)个点和顶点pi有边。树的顶点编号从0到n - 1。
第三行包含顶点的颜色的描述:n个整数X0,X1,…,Xn-1(xi为0或1)。如果xi为等于1,顶点i被着色成黑色。否则,顶点为白色。
输出
输出有多少种不同的分割方法。(注意取模)
copy from Vjudge(https://vjudge.net/problem/CodeForces-461B#author=prayerhgq)
□解析□
(不过是dalao 们讨论的结果)
1.算法
方法数量……树形结构,这摆明了就是要让我们用树形DP!这是树形DP的类型之一 —— 计数类。
2.建树
我还是一如既往地把树形结构改成了无向图,也就是树形图 —— 原因就是我不想找根!找根真的是一件很麻烦的事,所以定义无向图就可以随意选一个点作为根,所以就要存储一个双向边。这样就需要判断一种情况:
从此陷入死循环……
所以我们在函数的参数表里要定义一个fa(父亲节点),像这样:DFS(int u,int fa)
(好像透露了什么算法),然后判断u的”儿子”是否是fa,如果是,就说明这是双向边。或者你也可以定义一个bool数组vis,判断当前节点是否访问过,也就是一般的图的遍历判重方法,但是既然有树形图的特殊处理方法,为什么不用呢?
3.深度优先搜索
之前的函数名透露了一切……大多数的树形DP都是深度优先搜索的模板。由于当前u的状态需要由它的儿子v的状态倒推回来,所以我们需要先求出v的状态再计算u。
4.动态规划
状态定义(不解释):dp[i][0]:以i为根的子树里全是白色的情况下的方案数;dp[i][1]:以i为根的子树里只含有一个黑色节点的情况下的方案数。
那么就会针对u节点的颜色分类:
1. 黑色
u节点是黑色,就说明它的子树(包括u)里不可能全部是白色!所以这种情况dp[u][0]为0。那么现在就需要对u的儿子的颜色进行讨论——若儿子v为根的子树含有一个黑色节点,则它的方案数应该是dp[v][1]、dp[v][0]=0;否则是 dp[v][0]、dp[v][1]=0。综合一下,就是dp[v][1]+dp[v][0]。又根据乘法原理——u的方案总数就是
∏tree[u].size()i=0(dp[vi][1]+dp[vi][0])
∏
i
=
0
t
r
e
e
[
u
]
.
s
i
z
e
(
)
(
d
p
[
v
i
]
[
1
]
+
d
p
[
v
i
]
[
0
]
)
2. 白色
这样就很麻烦了……根节点是白色并不意味着它的子树的节点都是白色,所以需要分黑白两类!现在就需要处理儿子v的子树的颜色的情况:
令除v之外的所有儿子都是白色的方案数为F1,那么此情况的方案总数就是 F1*dp[v][1];
令除v以外的所有儿子包含一个黑色的方案总数为F2,那么可以选择隔开此子树,连接子树v,或者隔开子树v,连接另一个子树,此情况的方案总数为 F2*(dp[v][0]+dp[v][1]);
有没有一点奇怪?F1?F2?好像F1就是dp[u][0],F2就是dp[u][1](状态定义)。
□代码□
(不能让你们copy!!除非你们有耐心抄代码……( ̄▽ ̄)/)
老师告诉我们代码要自己理解了再默写( • ̀ω•́ )✧
The End
Thanks for reading!
-Lucky_Glass