Codeforces 766E
题意:给一棵树(1e5),每个节点上有对应权值(0<=ai<=1e6)定义树上两个节点间的距离为路径中节点的异或,求所有节点对间的距离和(包括节点自身也作为节点对,距离为节点权值)。
解题思路:
做了771C后这道题就有感觉了。关键在于将每个权值的二进制位拆开计算,以dp[u][i][0]和dp[u][i][1]记录到达节点u的子树上的节点的距离的第 i 位为0和1的个数有多少,维护计算就可以了。比较僵的是开始写的时候没有考虑单独一个节点作为一个节点对,所以在每个递归的最后面单独加上到全局变量res中。
每一个递归的最开始,用局部变量num数组记录u的权值的二进制形式,同时初始化dp[u]数组。对每个子节点的遍历转移:
先递归子节点,返回后则已计算完毕。则有如果u的第 i 位为1,dp[u][i][1]+=dp[v][i][0], dp[u][i][0]+=dp[v][i][1]; 否则dp[u][i][1]+=dp[v][i][1], dp[u][i][0]+=dp[v][i][0]; 这里应该不难理解。
至于统计答案,只计算为1的即可,即int cnt1=dp[u][i][1]*dp[v][i][0]+dp[u][i][0]*dp[v][i][1]; res+=1LL*cnt1*(1<<i);
见代码:(转移其实比771C好写一点感觉)
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> using namespace std; typedef long long ll; #define sqr(x) ((x)*(x)) const int N=1e5+10,M=21; int head[N],nxt[N<<1],to[N<<1],cnt; int n,a[N],dp[N][M][2]; ll res; void init(){ memset(head,-1,sizeof(head)); res=cnt=0; } void addEdge(int u,int v){ nxt[cnt]=head[u]; to[cnt]=v; head[u]=cnt++; } void dfs(int u,int pre){ int num[M]; for(int i=0;i<M;i++){ if(a[u]&(1<<i)) num[i]=1,dp[u][i][1]=1,dp[u][i][0]=0; else num[i]=0,dp[u][i][1]=0,dp[u][i][0]=1; } for(int e=head[u];~e;e=nxt[e]){ int v=to[e]; if(v==pre) continue; dfs(v,u); for(int i=0;i<M;i++){ int cnt1=dp[u][i][1]*dp[v][i][0]+dp[u][i][0]*dp[v][i][1]; res+=1LL*cnt1*(1<<i); if(num[i]){ dp[u][i][1]+=dp[v][i][0]; dp[u][i][0]+=dp[v][i][1]; }else{ dp[u][i][1]+=dp[v][i][1]; dp[u][i][0]+=dp[v][i][0]; } } } res+=a[u]; } int main(){ //freopen("in.txt","r",stdin); while(~scanf("%d",&n)){ for(int i=1;i<=n;i++) scanf("%d",a+i); init(); for(int i=1;i<n;i++){ int u,v; scanf("%d%d",&u,&v); addEdge(u,v); addEdge(v,u); } dfs(1,0); printf("%I64d\n",res); } return 0; }