【树形DP】爬

题目描述

树上有 n n n 个点,用 n − 1 n-1 n1 条边相连,除了根节点外,每个节点都有一个父亲,根节点记为 1 1 1 号节点,其他节点编号为 2 ∼ n 2\sim n 2n n n n 个点上分别放有一只蚂蚁,并且每只蚂蚁有一个属性 a i a_i ai,除了根节点的蚂蚁外,每只蚂蚁都可以选择向他的父亲结点爬动或者不爬(只能选择一次)。在所有蚂蚁选择完后,我们统计这些蚂蚁产生的快乐值:如果有多只蚂蚁在同一个点,则产生这些蚂蚁树形异或值的快乐值。如果某个点只有一只蚂蚁或者没有蚂蚁,则不产生快乐值。问所有方案的快乐值之和。 n ⩽ 1 0 5 n\leqslant 10^5 n105,对 1 0 9 + 7 10^9+7 109+7 取模。

题解

考虑到每个节点的快乐值与其他节点的快乐值没有关系,我们只考虑这个节点以及他的所有儿子。直接去枚举这些儿子爬动的方案,显然是 O ( 2 n ) \mathcal{O}(2^n) O(2n),于是我们考虑按位拆开,这样只有 0 , 1 0,1 0,1 两种值。
一个节点能产生快乐值,一定是由奇数个 1 1 1 和任意个 0 0 0 组成的。
考虑一些儿子向上爬动的方案,显然一个方案不会只对快乐值贡献一次,我们计算所有状态不固定的点为 x x x,那么这一个方案产生的快乐值的次数是 2 x 2^x 2x。状态固定的点包括这个节点以及他的所有儿子,和 1 1 1 节点,我们记不固定的点的个数为 d p i , 0 dp_{i,0} dpi,0
接下来我们讨论父亲节点的情况,他可以向上爬动(如果不是 1 1 1 节点的话),也可以不向上爬动(要讨论父亲的值是 0 0 0 还是 1 1 1)。记 c n t i , 0 / 1 cnt_{i,0/1} cnti,0/1 表示 x x x 节点的儿子在第 i i i 位下有多少个为 1 / 0 1/0 1/0 d p i , 1 dp_{i,1} dpi,1 表示 i i i 节点的快乐值总和。

  1. 父亲值 1 1 1,不向上爬动;儿子中需要选取偶数个 1 1 1 和任意个 0 0 0 上来,特别地,当我们没有选取 1 1 1 的儿子时,至少要有一个 0 0 0 上来。

d p x , 1 = ( 2 c n t x , 0 − 1 + ∑ i = 0 30 ∑ j = 2 c n t i , 0 [ j ≡ 0 ( m o d 2 ) ] × ( c n t i , 0 j ) × 2 i ) × 2 d p x , 0 dp_{x,1}=\left(2^{cnt_{x,0}}-1+\sum\limits_{i=0}^{30}\sum\limits_{j=2}^{cnt_{i,0}}[j\equiv 0\pmod 2]\times \binom{cnt_{i,0}}{j}\times 2^{i}\right)\times 2^{dp_{x,0}} dpx,1=(2cntx,01+i=030j=2cnti,0[j0(mod2)]×(jcnti,0)×2i)×2dpx,0

  1. 父亲值 0 0 0,不向上爬动;儿子中需要选取奇数个 1 1 1 和任意个 0 0 0 上来,没有特别情况。
  2. 不管父亲值多少,父亲向上爬动;儿子中需要选取奇数个 1 1 1 和任意个 0 0 0 上来,特别地,当选取一个 1 1 1 的时候,至少要有一个 0 0 0 的儿子上来,公式基本同上。

答案即为 ∑ i = 1 n d p i , 1 \sum\limits_{i=1}^ndp_{i,1} i=1ndpi,1,但是注意组合数的逆元,快速幂最好提前预处理,不要多次计算,这样会跑的比较快。

疑似有背包 DP 解法,我不会。

#include<bits/stdc++.h>
#define int long long
#define mid ((l+r)>>1)
#define fir first
#define sec second
#define lowbit(i) (i&(-i))
using namespace std;
const int N=2e5+5;
const int inf=1e18;
struct edge{int to,nxt,l;}a[N];
int head[N],cnt,dp[N][2],jc[N],f[N],n,siz[N],invjc[N];
void add(int x,int y){
    a[++cnt].to=y;
    a[cnt].nxt=head[x];
    head[x]=cnt;
}
inline int read(){
    char op=getchar();
    int w=0,s=1;
    while(op<'0'||op>'9'){
        if(op=='-') s=-1;
        op=getchar();
    }
    while(op>='0'&&op<='9'){
        w=(w<<1)+(w<<3)+op-'0';
        op=getchar();
    }
    return w*s;
}
const int mod=1e9+7;
int Mul(int a,int b){return (a%mod*b%mod)%mod;}
int Add(int a,int b){return (a+b)%mod;}
int Dec(int a,int b){return (a-b+mod)%mod;}
int Pow(int a,int k){
    int ans=1;
    while(k){
        if(k&1) ans=Mul(ans,a);
        a=Mul(a,a);
        k>>=1;
    }
    return ans;
}
int inv(int x){return Pow(x,mod-2);}
void exgcd(int a,int b,int &x,int &y){
    if(b==0){
        x=1,y=0;
        return;
    }
    exgcd(b,a%b,x,y);
    int t=x;
    x=y;
    y=t-a/b*y;
}
int C(int n,int m){
    if(n==0) return 1;
    return Mul(Mul(jc[m],invjc[m-n]),invjc[n]);
}
void dfs(int x,int fa){
    int cnt[31][2];
    for(register int i=0;i<=30;i++) cnt[i][0]=cnt[i][1]=0;
    dp[x][0]=n-1;
    if(x!=1) dp[x][0]--;
    siz[x]=1;
    for(register int i=head[x];i;i=a[i].nxt){
        int y=a[i].to;
        if(y==fa) continue;
        dfs(y,x);
        siz[x]+=siz[y];
        dp[x][0]--;
        for(register int j=0;j<=30;j++){
            if((1ll<<j)&f[y]) cnt[j][1]++;
            else cnt[j][0]++;
        }
    }
    dp[x][0]=Pow(2,dp[x][0]);
    if(siz[x]==1){
        dp[x][1]=0;
        return;
    }
    for(register int i=0;i<=30;i++){
        int ans=0;
        int p=Pow(2,cnt[i][0]);
        if((1ll<<i)&f[x]){
            for(register int j=2;j<=cnt[i][1];j+=2) ans=Add(ans,C(j,cnt[i][1]));
            ans=Mul(ans,p);
            ans=Add(ans,p-1);
            ans=Mul(ans,Mul(1ll<<i,dp[x][0]));
        }else{
            for(register int j=1;j<=cnt[i][1];j+=2) ans=Add(ans,C(j,cnt[i][1]));
            ans=Mul(ans,p);
            ans=Mul(ans,Mul(1ll<<i,dp[x][0]));
        }
        dp[x][1]=Add(dp[x][1],ans);
        if(x!=1){//父亲爬一层
            ans=0;
            for(register int j=3;j<=cnt[i][1];j+=2) ans=Add(ans,C(j,cnt[i][1]));
            ans=Mul(ans,p);
            ans=Add(ans,Mul(C(1,cnt[i][1]),p-1));
            ans=Mul(ans,Mul(1ll<<i,dp[x][0]));
            dp[x][1]=Add(dp[x][1],ans);
        }
    }
}
signed main(){
    n=read();
    for(register int i=1;i<=n;i++) f[i]=read();
    jc[0]=1;
    invjc[0]=1;
    for(register int i=1;i<=100000;i++){
        jc[i]=Mul(jc[i-1],i);
        invjc[i]=inv(jc[i]);
    }
    for(register int i=2;i<=n;i++){
        int x=read();
        add(i,x),add(x,i);
    }
    dfs(1,0);
    int ans=0;
    for(register int i=1;i<=n;i++) ans=Add(ans,dp[i][1]);
    printf("%lld",ans);
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值