CodeForces 482 D.Random Function and Tree(树形DP)

Description

给出一个以 1 为根的n个节点的树,第 i 个节点的父亲节点为pi,初始时所有节点为红色,问执行以下代码会出现多少种不同的染色方案,结果模 109+7

count = 0 // global integer variable 

rnd() { // this function is used in paint code
    return 0 or 1 equiprobably
}

paint(s) {
    if (count is even) then paint s with white color
    else paint s with black color

    count = count + 1

    if rnd() = 1 then children = [array of vertex s children in ascending order of their numbers]
    else children = [array of vertex s children in descending order of their numbers]

    for child in children { // iterating over children array
        if rnd() = 1 then paint(child) // calling paint recursively
    }
}

Input

第一行输入一整数 n 表示节点数,之后n1个数 p2,...,pn(2n105,1pi<i)

Output

输出不同的染色方案数,结果模 109+7

Sample Input

4
1 2 1

Sample Output

8

Solution

dp[u][0] dp[i][1] 分别为染以 u 节点为根的子树偶数个点和奇数个点的方案数,对于节点u,对于每种状态,其每个儿子有三个选择:进去染奇数点,进去染偶数点,不进去,而每种状态又有两种情况:从左到右,从右到左的,显然这两者相等,转移也很简单,但是有交集,关键在于求交集

假设经过 u 节点染了奇数个点,则除u外染了偶数点,想要染偶数点且两种顺序下相同,只能是每个儿子要么不染要么染偶数点(代码里的 a )(否则必然有偶数个染奇数点的儿子,考虑第一个染奇数点的儿子,从左到右进去时染了偶数点,从右往左进去时染了奇数点,显然不同)

假设经过u节点染了偶数个点,则除 u 外染了奇数点,想要染奇数点且两种顺序下相同,只能是每个儿子要么不染要么染奇数点,且染奇数点的儿子为奇数个,考虑求染了奇数个点的儿子为奇数的方案数c和为偶数的方案数 b ,这两个值相互转移即可 (否则必然存在染偶数点儿子,这个儿子前面染奇数点的兄弟和后面染奇数点的兄弟个数奇偶性不同,那么从左到右和从右到左染的点数奇偶性也不同)
注意到pi<i,故一个点的儿子编号必然小于其编号,进而不需要 dfs ,直接从小到大枚举点的编号即可转移

Code

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<ctime>
using namespace std;
typedef long long ll;
typedef pair<int,int>P;
const int INF=0x3f3f3f3f,maxn=100005;
#define mod 1000000007
void add(int &x,int y)
{
    x=x+y>=mod?x+y-mod:x+y;
} 
int n,dp[maxn][2];
vector<int>g[maxn];
int main()
{
    scanf("%d",&n);
    for(int i=2;i<=n;i++)
    {
        int p;
        scanf("%d",&p);
        g[p].push_back(i);
    }
    for(int u=n;u>=1;u--)
    {
        dp[u][1]=1,dp[u][0]=0;
        if(!g[u].size())continue;
        for(int i=0;i<g[u].size();i++)
        {
            int v=g[u][i];
            int t0=dp[u][0],t1=dp[u][1];
            add(dp[u][1],(ll)t1*dp[v][0]%mod);
            add(dp[u][1],(ll)t0*dp[v][1]%mod);
            add(dp[u][0],(ll)t0*dp[v][0]%mod);
            add(dp[u][0],(ll)t1*dp[v][1]%mod);
        }
        add(dp[u][0],dp[u][0]);
        add(dp[u][1],dp[u][1]);
        int a=1,b=1,c=0;
        for(int i=0;i<g[u].size();i++)
        {
            int v=g[u][i];
            add(a,(ll)a*dp[v][0]%mod);
            int tb=b,tc=c;
            add(b,(ll)tc*dp[v][1]%mod);
            add(c,(ll)tb*dp[v][1]%mod);
        }
        add(dp[u][0],mod-c);
        add(dp[u][1],mod-a);
    }
    printf("%d\n",(dp[1][1]+dp[1][0])%mod);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值