[CodeForces-543D]Road Improvement
题目大意:
给你一棵n个结点的树,请你搞一些破坏。
你可以从中切掉一些边,为了掩人耳目,你需要保证任何一个结点到根结点的路径上最多只能有一条边被切断。
问以1..n号结点为根时,分别有多少种搞破坏的方案?
思路:
考虑以1为根的情况,用f[i]表示以i为根的子树中合法的搞破坏方案数,j是i的子树,则状态转移方程为:
f[i]=prod{f[j]+1},加的那个1表示当j的子树没有被破坏的情况,此时可以破坏掉边(i,j)。
于是这就变成了入门难度的题目。
然而现在我们要把每个点都当作根求一遍,O(n^2)做显然不可行,怎么办?
考虑j是i的一个孩子,原本是以i为根的,现在改成以j为根。
此时实际上变化的值只有f[i]和f[j]。
因此我们每次只需要修改f[i]和f[j]的值即可。
而且修改也只需要把i和j的兄弟的子树当作j的子树处理即可。
这些兄弟原来就是j的兄弟,一些则是将i提到根以后新增加出来的子树,原来的子树可以先预处理出来,用数组sib表示。
新加进来的结点一边换根一边记录,假设是new。
所以换根时的dp方程为f[j]=f[j]*(new*sib[i]+1)。
因为f数组换完根就废了,因此我们可以直接在f上记录new。
又因为题目本来就是按照拓扑序告诉你的,所以你连DFS都不用写。
1 #include<cstdio> 2 #include<cctype> 3 #include<vector> 4 typedef long long int64; 5 inline int getint() { 6 register char ch; 7 while(!isdigit(ch=getchar())); 8 register int x=ch^'0'; 9 while(isdigit(ch=getchar())) x=(((x<<2)+x)<<1)+(ch^'0'); 10 return x; 11 } 12 const int N=200001,mod=1e9+7; 13 std::vector<int> e[N]; 14 int f[N],par[N],sib[N]; 15 int main() { 16 int n=getint(); 17 for(register int i=2;i<=n;i++) { 18 e[par[i]=getint()].push_back(i); 19 } 20 for(register int x=n;x;x--) { 21 f[x]=1; 22 for(register std::vector<int>::iterator i=e[x].begin();i!=e[x].end();i++) { 23 const int &y=*i; 24 sib[y]=f[x]; 25 f[x]=(int64)f[x]*(f[y]+1)%mod; 26 } 27 int tmp=1; 28 for(register std::vector<int>::reverse_iterator i=e[x].rbegin();i!=e[x].rend();i++) { 29 const int &y=*i; 30 sib[y]=(int64)sib[y]*tmp%mod; 31 tmp=(int64)tmp*(f[y]+1)%mod; 32 } 33 } 34 sib[1]=1; 35 for(register int i=1;i<=n;i++) { 36 const int tmp=(int64)sib[i]*f[par[i]]%mod+1; 37 printf("%d ",int((int64)f[i]*tmp%mod)); 38 f[i]=tmp; 39 } 40 return 0; 41 }