ZJNU - 2278丨树形dp

ZJNU - 2278
题意:
定义一个树上两点间路径权值是路径上各点的连续异或。现在给出树上所有点权,求树上任意两点间简单路径权值的总和(包括自己到自己的情况,这个权值就视为点权)

思路:
我模拟了第三个样例的思考过程,首先要注意异或不满足分配律,对于这样的情况我们可以按照每一个二进制位来进行分析:

记录从节点u出发,能构成权值的第j位是1/0的链的个数 d p ( u , j , 0   o r   1 ) dp(u,j,0 \ or \ 1) dp(u,j,0 or 1)这可以在父子节点之间通过累加来更新。每次更新的时候,可以顺便计算答案,以为知道的是个数,所以做乘法就可以得到穿过父子节点的链条的个数了。有了个数,乘以对应的二进制位权值就能得到部分答案。

本来做一次搜索,回溯的时候更新就可以了,但是由于本校oj撑不住那么大的递归,所以要换成非递归的写法。这里提供一个思路是,栈模拟深搜遍历,记录dfs序,倒序经行一遍即可。

(下面的每个记录,第一行表示编号,第二行表示权值,第三四行是dp值)
在这里插入图片描述在这里插入图片描述在这里插入图片描述
在这里插入图片描述在这里插入图片描述代码:

#include<iostream>
#include<algorithm>
#include<string.h>
#include<stack>
#define mp(x,y) make_pair(x,y)
#define _1 first
#define _2 second
using namespace std;
const int MAXN = 1e5+7;
typedef long long ll;
typedef pair<int,int> PII;
ll a[MAXN],n;
ll two[23];
ll dp[MAXN][25][2];

PII dfsc[MAXN];
stack<PII> dfs;

//----graph----
int head[MAXN],cntEd;
struct Edge{
    int v,nx;
}e[MAXN<<1];
inline void addEd(int u,int v){
    e[++cntEd]={v,head[u]};
    head[u]=cntEd;
}
//--------------

void DFS(int u,int f,int &cnt){
    dfs.emplace(mp(u,f));
    while(!dfs.empty()){
        u=dfs.top()._1;
        f=dfs.top()._2;
        dfsc[++cnt]=dfs.top();
        dfs.pop();

        for(int v,i=head[u];~i;i=e[i].nx){
            v=e[i].v;
            if(v==f)continue;
            dfs.emplace(mp(v,u));
        }
    }
}
void DFSC(int u,int f,ll &ans){
    int cntdfs=0;
    DFS(u,f,cntdfs);
    for(int u,v,i=cntdfs;i>0;i--){
        v=dfsc[i]._1;
        u=dfsc[i]._2;
        for(int j=0;j<23;j++){
            ans+=two[j]*dp[u][j][0]*dp[v][j][1];
            ans+=two[j]*dp[u][j][1]*dp[v][j][0];

            if(a[u]&two[j]){
                dp[u][j][1]+=dp[v][j][0];
                dp[u][j][0]+=dp[v][j][1];
            }else{
                dp[u][j][1]+=dp[v][j][1];
                dp[u][j][0]+=dp[v][j][0];
            }
        }
    }
}



int main(){
    ll ans=0;
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);


    cin>>n;
    memset(head,-1,sizeof head);
    two[0]=1;
    for(int i=1;i<23;i++)
        two[i]=two[i-1]<<1;

    for(int i=1;i<=n;i++){
        cin>>a[i];
        ans+=a[i];
        for(int s=a[i],j=0;j<23;j++,s>>=1){
            dp[i][j][s&1]++;
        }
    }
    for(int u,v,i=1;i<n;i++){
        cin>>u>>v;
        addEd(u,v);
        addEd(v,u);
    }
    DFSC(1,0,ans);
    cout<<ans<<endl;

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值