zoukankan      html  css  js  c++  java
  • [AcWing 758. 切割树] 树形DP

    题目链接
    题意:
    是给一棵树,每个节点有颜色,只有白色和黑色,切成若干部分,使得每部分只有一个白色点。求切割方案。输出对1000000007取模后的结果。

    输入格式

    第一行仅包含一个正整数n,表示树的结点数量。1≤n≤105
    
    第二行包含n-1个数字,第 i 个数字表示第 i 个结点的根,我们认为 0 号结点是整棵树的根.第 i 个数字不超过 i,即第 i 个结点的根一定是编号小于 i 的结点。
    
    第三行包含n个数字,第 i 个数字表示第 i-1 个结点的颜色,0表示白色,1表示黑色。
    

    输出格式

    输出一个整数表示对1000000007取模后的结果。
    

    输入样例1:

    3
    0 0
    1 0 0
    

    输出样例1:

    2
    

    输入样例2:

    10
    0 0 1 2 0 5 1 2 3
    1 0 0 1 0 0 1 1 0 1
    

    输出样例2:

    3
    

    思路: 树形DP

    状态设计

     f[i][0]代表i节点切割出去的儿子都符合条件,且 i 点所在的连通块有白色点。
     f[i][1]代表i节点切割出去的儿子都符合条件,且 i 点所在的连通块没有白色点
    

    考虑第i个节点的情况
    1. 当 i 点是白色点的时候。f[i][1] = 0因为他不可能存在一个连通块内并且不包含白色。f[i][0]由其所有的儿子的方案累乘。具体来说,对于一个儿子j,可以选择切割这条边,代表这个儿子所在的连通块包含白色,+f[j][0],也可以不切这条边,代表儿子所在的连通块没有白色, +f[j][1]
    在这里插入图片描述

    2. 当 i 点是黑色点的时候。f[i][1]也是同理由其儿子选择切割与不切割转移来,f[i][0]则是由贡献白点的儿子转移,由于只能有一个儿子贡献白点,其他儿子贡献的是全黑点的连通块,或者切掉,所以各个儿子贡献的方案之间是加法关系

    这里的由于需要 i != j 的乘法积,最好分别用前缀积、后缀积数组保存起来


    代码中有详细解释,注意每次计算结果是否超出范围,常取模

    #include<iostream>
    #include<cstring>
    using namespace std;
    typedef long long LL;
    const int N=1e6+5,mod=1e9+7;
    int n;
    int len,e[N],h[N],ne[N];
    bool color[N];
    int f[N][2];//f[i][0]表示包含i这个节点的树里面没有白色的节点,f[i][1]表示有一个白色节点
    int q[N],l[N],r[N];//l前缀数组,r后缀数组,q用来保存取出来的儿子
    //邻接表存储树,添加u->v这条边
    void add(int u,int v) {
    	e[len]=v;
    	ne[len]=h[u];
    	h[u]=len++;
    }
    void dfs(int u) {
    	//先递归的计算所有的子节点
    	for(int i=h[u]; ~i; i=ne[i])
    		dfs(e[i]);
    	if(color[u]==0) {//如果当前是白色,显然f[u][0]=0,f[u][1]=1;
    		f[u][0]=0,f[u][1]=1;
    		//下面去计算出f[u][1];
    		for(int i=h[u]; ~i; i=ne[i]) {//遍历u的所有的儿子
    			int j=e[i];//取出儿子的节点
    			f[u][1]=(LL)f[u][1]*(f[j][1]+f[j][0])%mod;//u这个节点本身计算白色,现在要去求出所有儿子没有贡献白色的节点
    			//1. 就是j这个节点没有白色 即f[j][0];
    			//2. j这个有白色,但是i和j断开,断开后j这个节点必须要有一个白色节点的方案数,即f[j][1];
    			//显然两种情况都只能存在一种,所以此时方案数x=f[j][1]+f[j][0];
    			//而每个儿子互不影响,情况相同,所以f[u][1]=∏(xj) ,0<=j<=k,k为u的儿子数
    		}
    	} else {
    		//当前是黑色的节点,考虑f[u][0],同样f[u][0]的初值为1;
    		f[u][0]= 1,f[u][1]=0;
    		//因为这里不管是计算f[u][0]还是计算f[u][1]都需要用到u的儿子;
    		//所有取出所有的儿子放入q数组中,用k记录儿子数量
    		int k=0;
    		for(int i=h[u]; ~i; i=ne[i]) {
    			q[++k]=e[i];
    		}
    		//计算f[u][0],其实f[u][0]的计算与"当u为白色的时候,去算f[u][1]" 的思路类似
    		// 即先找出u的儿子f[i][0]的方案数,然后找到f[i][1],但是断开他们
    		for(int i=1; i<=k; i++)
    			f[u][0]=(LL)f[u][0]*(f[q[i]][1]+f[q[i]][0])%mod;
    		//下面是最难的,计算f[u][1],因为当前的u为黑色,它能拥有白色只能说明白色来自于儿子所在的树贡献出来,且只能有一个
    		//不妨设第个j儿子贡献白色,则其他的儿子都要与u断开,且其他的儿子必须包含白色
    		//此时方案数 x = f[j][1] * [∏(f[i][1]+f[i][0]),1<=i<=k,i!=j,k为所有u的儿子]
    		//因此所有的方案数f[u][1]=∑x (1<=x<=k)
    
    		//所以为了方便就要预处理,先计算除去j的前j-1的乘积和后j+1的乘积,即j的前缀数组、后缀数组;
    		//下面计算前缀、后缀数组
    		l[0]=r[k+1]=1;
    		for(int i=1; i<=k; i++)
    			l[i]=(LL)l[i-1]*(f[q[i]][1]+f[q[i]][0])%mod;
    		for(int i=k; i>=1; i--)
    			r[i]=(LL)r[i+1]*(f[q[i]][1]+f[q[i]][0])%mod;
    
    		//计算f[u][1]
    		for(int i=1; i<=k; i++)
    			f[u][1]=(f[u][1]+(LL)l[i-1]*r[i+1]%mod*f[q[i]][1])%mod;
    	}
    }
    int main() {
    	cin>>n;
    	memset(h,-1,sizeof(h));
    	int x;
    	for(int i=1; i<n; i++) {
    		cin>>x;
    		add(x,i);
    	}
    	for(int i=0; i<n; i++) {
    		cin>>color[i];
    	}
    	dfs(0);
    	cout<<f[0][1]<<endl;
    	return 0;
    }
    
  • 相关阅读:
    tinymce原装插件源码分析(二)-link
    tinymce原装插件源码分析(一)-hr
    pyinstall 常见错误
    matlab Time-domain analysis 渐进式或者实时获取仿真值
    初识python和pycharm
    自定义指令详解
    Vue核心知识一览
    多维数组 转化为 一维数组
    js面试之数组的几个不low操作
    js如何操作或是更改sass里的变量
  • 原文地址:https://www.cnblogs.com/zdw20191029/p/14553385.html
Copyright © 2011-2022 走看看