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;
}