中档的树形dp题
问题描述 DZY有一棵nnn个结点的无根树,结点按照1∼n1\sim n1∼n标号。
DZY喜欢树上的连通集。一个连通集SSS是由一些结点组成的集合,满足SSS中任意两个结点u,vu,vu,v能够用树上的路径连通,且路径上不经过SSS之外的结点。显然,单独一个结点的集合也是连通集。
一个连通集的大小定义为它包含的结点个数,DZY想知道所有连通集的大小之和是多少。你能帮他数一数吗?
答案可能很大,请对109+710^9 + 7109+7取模后输出。
输入描述 第一行ttt,表示有ttt组数据。 接下来ttt组数据。每组数据第111行一个数nnn。第2∼n2\sim n2∼n行中,第iii行包含一个数pip_ipi,表示iii与pip_ipi有边相连。(1≤pi≤i−1,2≤i≤n1\le p_i \le i-1,2\le i\le n1≤pi≤i−1,2≤i≤n)
(n≥1n\ge 1n≥1,所有数据中的nnn之和不超过200000200000200000)
输出描述 每组数据输出一行答案,对109+710^9 + 7109+7取模。
输入样例 2 1 5 1 2 2 3
输出样例 1 42
题解: rootnum[i]表示以i为根节点的连通集的大小和; sum[i]表示以i为根节点的连通集的个数; 对节点x而言对每个子节点flag进行以下操作 rootnum[x]=(rootnum[x](sum[flag]+1))%mod+sum[x]rootnum[flag]%mod; sum[x]=sum[x](sum[flag]+1)%mod; 其中rootnum[x](sum[flag]+1)表示每个新加入的子节点的连通集个数(还有空集)数与原本连通集(x及之前x子节点形成的连通集)进行组合,表示当新子节点加入时对原本连通集大小的增幅(或者说贡献); 其中sum[x]rootnum[flag]表示新加入的连通集(新加入的子节点及其子节点的连通集)与原本连通集的个数进行组合,,表示当新子节点加入时原本连通集个数对新加入子节点的连通集大小的贡献; 其中sum[x]=sum[x](sum[flag]+1)更新了以i为根节点且包含i的连通集个数;
#include<cstdio>
#include <iostream>
#include <cmath>
#include <vector>
using namespace std;
vector<int>ve[200005];
long long all;
const int mod=1e9+7;
long long sum[200005],rootnum[200005];
int dfs(int x,int father)
{
rootnum[x]=1,sum[x]=1;
for(int i=0;i<ve[x].size();i++)
{
int flag=ve[x][i];
if(flag==father)
{
continue;
}
dfs(flag,x);
rootnum[x]=(rootnum[x]*(sum[flag]+1))%mod+sum[x]*rootnum[flag]%mod;
sum[x]=sum[x]*(sum[flag]+1)%mod;
}
all=(all+rootnum[x])%mod;
}
int main()
{
int t,n,read;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
all=0;
for(int i=1;i<=n;i++)
{
ve[i].clear();
}
for(int i=2;i<=n;i++)
{
scanf("%d",&read);
ve[i].push_back(read);
ve[read].push_back(i);
}
dfs(1,0);
printf("%I64d\n",all);
}
return 0;
}