482 D. Random Function and Tree

题意:给定一棵树,初始状态下所有树的节点都是红色,现在按照下述程序递归对树进行染色,问所有染色的方法数.
染色方法:
int p()
{}//等可能地返回0和1
int count;//全局变量,初始值为0.
void paint(s)
{
	int v = p();
	if(v == 0)	将点s染为白色.
	else	将点s染为黑色.
	v = p();
	if(v == 0)	将s的孩子按序号从大到小放在数组a[]中.
	else	将s的孩子按序号从小到大放在数组a[]中.
	for(s的每一个孩子)
	{
		if(p())	paint(v);
	}
}
解法:首先注意到,对于以点u为根的一棵子树,将点u染成白色和黑色的方法都一一对应.所以对于点u,有意义的状态是:dp[u][0]:以u为根的子树中包含点u共染了偶数个点,dp[u][1]:以u为根的子树中包含点u共染了奇数个点.最后的答案就是dp[1][0]+dp[1][1].
先考虑从左往右染色,则到u的一个孩子v时有以下转移方程:(注意奇偶相互对应)
ll add_dp0 = dp[u][0] * dp[v][0] % MOD + dp[u][1] * dp[v][1] % MOD;
ll add_dp1 = dp[u][1] * dp[v][0] % MOD + dp[u][0] * dp[v][1] % MOD;
dp[u][0] = (dp[u][0] + add_dp0) % MOD;//选或者不选
dp[u][1] = (dp[u][1] + add_dp1) % MOD;
从左往右和从右往左是相互对应的过程,所以对dp[u][0]和dp[u][1]都乘以2.
重复计数的部分:奇数个染了奇数个的子树的方法数和全部都是染了偶数个的子树,再次dp处理即可.
转移方程:
int v = g[u][i];
ll val = dp2[0][0];
dp2[0][0] = (dp2[0][0] + dp[v][0] * dp2[0][1]) % MOD;
dp2[0][1] = (dp2[0][1] + dp[v][0] * val) % MOD;
val = dp2[1][0];
dp2[1][0] = (dp2[1][0] + dp[v][1] * dp2[1][1]) % MOD;
dp2[1][1] = (dp2[1][1] + dp[v][1] * val) % MOD;
#include <cstdio>
#include <cstring>
#include <set>
#include <vector>
#include <algorithm>
#include <string>
#include <map>
#include <iostream>
#include <iomanip>
using namespace std;

typedef long long ll;

const int MAXN = 100005;
const int MOD = 1000000007;
vector<int> g[MAXN];
int n;

ll dp[MAXN][2];
ll dp2[2][2];

void dfs(int u)
{
    dp[u][0] = 0, dp[u][1] = 1;
    int size = (int)g[u].size();
    
    if (size == 0)  return;
    
    for (int i = 0; i < size; ++ i) dfs(g[u][i]);
    
    for (int i = 0; i < size; ++ i) {
        int v = g[u][i];
        ll add_dp0 = dp[u][0] * dp[v][0] % MOD + dp[u][1] * dp[v][1] % MOD;
        ll add_dp1 = dp[u][1] * dp[v][0] % MOD + dp[u][0] * dp[v][1] % MOD;
        dp[u][0] = (dp[u][0] + add_dp0) % MOD;
        dp[u][1] = (dp[u][1] + add_dp1) % MOD;
    }
    
    dp[u][0] = (dp[u][0] + dp[u][0]) % MOD;
    dp[u][1] = (dp[u][1] + dp[u][1]) % MOD;
    
    dp2[0][0] = dp2[1][0] = 1;
    dp2[0][1] = dp2[1][1] = 0;
    
    for (int i = 0; i < size; ++ i) {
        int v = g[u][i];
        ll val = dp2[0][0];
        dp2[0][0] = (dp2[0][0] + dp[v][0] * dp2[0][1]) % MOD;
        dp2[0][1] = (dp2[0][1] + dp[v][0] * val) % MOD;
        val = dp2[1][0];
        dp2[1][0] = (dp2[1][0] + dp[v][1] * dp2[1][1]) % MOD;
        dp2[1][1] = (dp2[1][1] + dp[v][1] * val) % MOD;
    }
    
    dp[u][0] = (dp[u][0] + MOD - dp2[1][1]) % MOD;
    dp[u][1] = (dp[u][1] + MOD - dp2[0][0] + MOD - dp2[0][1]) % MOD;
}

int MAIN()
{
    cin >> n;
    int p;
    for (int i = 2; i <= n; ++ i) {
        cin >> p;
        g[p].push_back(i);
    }
    dfs(1);
    cout << (dp[1][0] + dp[1][1]) % MOD << endl;
    return 0;
}

int main()
{
    ios::sync_with_stdio(false);
    cout << fixed << setprecision(16);
    return MAIN();
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值