G:Deblo(树上边异或和)

原题: http://acm.zjnu.edu.cn/CLanguage/showproblem?problem_id=2278

题意:

树上点权,一条边的值为树上所有点权的异或。求所有边的值之和。

解析:

将值拆成二进制位,记录每个二进制位出现的次数。例如有三条边权的二进制分别为:101,111,011,那么第1位3次,第2位2次,第3位2次。

树形dp, d p [ i ] [ j ] [ k ] = v dp[i][j][k]=v 表示到第点 i i ,第 j j 位为 k k 的边的数量为 v v 。因为折线不能往上更新了,所以 d p dp 记录的其实是非折线的数量。

对于一个点 f f ,儿子有 u 1 , u 2 , u 3... u1,u2,u3... ,将 u u f f 各边的异或记录,记为 u f u*f ,而算进答案还有 u x , u y , f u_x,u_y,f 三个形成的折线( u x u y f u_x*u_y*f )。这个每次 u i u_i u i 1 f , u i 2 f . . . u_{i-1}*f,u_{i-2}*f... 记录即可。

这个oj不能bfs太深,所以会RE几个案例

#include<bits/stdc++.h>
using namespace std;

const int N=1e5+5;
#define LL long long
LL dp[N][35][2];
struct node
{
    int to,next;
}e[N*2];
int cnt,head[N],a[N];
void add(int x,int y)
{
    e[cnt].to=y;
    e[cnt].next=head[x];
    head[x]=cnt++;
}
LL ans=0;
void dfs(int x,int fa){
    for(int i=head[x];~i;i=e[i].next){
        int ne=e[i].to;
        if(ne==fa)
            continue;
        dfs(ne,x);
        for(int j=1;j<=22;j++){
            ans+=(1<<j-1)*dp[x][j][1]*dp[ne][j][0],
            ans+=(1<<j-1)*dp[x][j][0]*dp[ne][j][1];

            if(a[x]&(1<<j-1))
                dp[x][j][0]+=dp[ne][j][1],dp[x][j][1]+=dp[ne][j][0];
            else
                dp[x][j][0]+=dp[ne][j][0],dp[x][j][1]+=dp[ne][j][1];
        }
    }
}

int main(){
    memset(head,-1,sizeof(head));
    int n,x,y;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        for(int j=1;j<=22;j++)
        {
            if((a[i]&(1<<j-1)))
                dp[i][j][1]=1,ans+=(1<<j-1);
            else
                dp[i][j][0]=1;
        }
    }
    for(int i=1;i<n;i++)
        scanf("%d%d",&x,&y),add(x,y),add(y,x);
    dfs(1,0);
    printf("%LLd\n",ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/jk_chen_acmer/article/details/88767338