codeforces 766E 二进制思想dp

官方题解看了半天硬是没懂…最后自己琢磨出来了。 这题主要是二进制的思想,因为异或是按位运算,可以把每个数按照二进制拆成若干个0和1,对每个”位”建树,然后用动态规划的思想求和。
具体实现方法:
以1为根建dfs树。想象从1到一个叶节点的一条路,又有1到另一个叶节点的一条路。假设我们暴力做,那答案里肯定得分别把这两个结果算出来加进去。那我们现在动态规划,就是尽量找到这两条路的公共部分,然后只算一次(就可以节约时间)。那么怎么找到这个公共部分呢?第一步是把复杂的路简单化:先是用二进制的方式把每个数按位拆开,这样一条路就变成了logn条路,每条上面都只有0和1。第二步就是合并。就比方说刚才的那条路,是从1到2再到3。算它,那就得先算1到2的路(a^b^c=(a^b)^c),而1到2的路本来也是要算的。那我们就把它合并成个1到2的加上个2到3的。到底有几个?找个递推关系dp[][]推一下就知道了呗。我的代码里dp[n][i][j]表示的是从第n个节点开始的,二进制第i为的结果是j的路的总条数,当然也可以选择别的。
以下是ac代码。

#include<stdio.h>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn=100010;
int N,S[maxn];
long long dp[maxn][20][2],ans;
int nxt[maxn*2],to[maxn*2],fst[maxn],cnt;
void dfs(int now,int pre){
    for(int i=0;i<20;i++)
        dp[now][i][S[now]>>i&1]=1;
    for(int p=fst[now];p;p=nxt[p])if(to[p]!=pre){
        int z=to[p];
        dfs(z,now);
        for(int i=0;i<20;i++){
            ans+=((long long)dp[z][i][0]*dp[now][i][1]+(long long)dp[z][i][1]*dp[now][i][0])<<i;
            bool t=S[now]>>i&1;
            dp[now][i][0]+=dp[z][i][t];dp[now][i][1]+=dp[z][i][!t];
        }
    }
}
int main(){
    int u,v;
    scanf("%d",&N);
    for(int i=1;i<=N;i++)
        scanf("%d",S+i),ans+=S[i];
    for(int i=1;i<N;i++){
        scanf("%d%d",&u,&v);
        nxt[++cnt]=fst[u];to[cnt]=v;fst[u]=cnt;
        nxt[++cnt]=fst[v];to[cnt]=u;fst[v]=cnt;
    }
    dfs(1,1);
    printf("%I64d",ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值