题目描述
树上有 n n n 个点,用 n − 1 n-1 n−1 条边相连,除了根节点外,每个节点都有一个父亲,根节点记为 1 1 1 号节点,其他节点编号为 2 ∼ n 2\sim n 2∼n。 n n n 个点上分别放有一只蚂蚁,并且每只蚂蚁有一个属性 a i a_i ai,除了根节点的蚂蚁外,每只蚂蚁都可以选择向他的父亲结点爬动或者不爬(只能选择一次)。在所有蚂蚁选择完后,我们统计这些蚂蚁产生的快乐值:如果有多只蚂蚁在同一个点,则产生这些蚂蚁树形异或值的快乐值。如果某个点只有一只蚂蚁或者没有蚂蚁,则不产生快乐值。问所有方案的快乐值之和。 n ⩽ 1 0 5 n\leqslant 10^5 n⩽105,对 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 和任意个 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,0−1+i=0∑30j=2∑cnti,0[j≡0(mod2)]×(jcnti,0)×2i)×2dpx,0
- 父亲值 0 0 0,不向上爬动;儿子中需要选取奇数个 1 1 1 和任意个 0 0 0 上来,没有特别情况。
- 不管父亲值多少,父亲向上爬动;儿子中需要选取奇数个 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=1∑ndpi,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);
}