2019 ACM/ICPC 全国邀请赛(西安)J And And And (树DP?)

题意:

给定一棵n个点的树和每条边的边权wi,保证父节点的编号小于子节点,定义为树上u到v的路径上所有点的集合,定义为树上u到v的路径上所有边的异或和,求:

其中n<=1e5,wi<=1e18

https://nanti.jisuanke.com/t/39277

题解:

我不会树DP,纯粹是从算贡献的角度想的,因此不知道这是否算是树DP。

首先分析要求的式子,(u,v)是路径的两个端点,(u',v')是路径中的点集中的点,由可知求了(u,v)就不需要求(v,u),即端点相同的路径只需要算一次,由可知对于相同端点的路径中满足的点对同样只算一次。

先考虑暴力,需要枚举路径的端点(u,v),再枚举路径点集中满足异或和为零的(u',v'),复杂度显然爆炸。

凡是这种情况都需要想到算贡献,即通过一些信息使得计算次数尽可能少。

首先考虑如何判断两个点路径的异或和是否为0。

这个很简单,因为异或是可逆的,假设u到1的异或和为s[u],v到1的异或和为s[v](显然1到v的异或和也为s[v]),则有u到v的异或和为s[u]^s[v],即u先到1再到v和u直接到v的结果是一样的。所以只需要算出每个点到根的异或和,就能求出任意两点路径上的异或和。

那么路径(边权)异或和为0,就相当于两个点到根的异或和相等

接下来考虑怎么对这样的点算贡献。

假设两个满足条件的点编号大的称为左端,另一个称为右端。

那么这两个点会被包含的次数即为左端及更左端的节点数乘以右端及更右端的节点数。(此处的更X端显然指不会出现在 左端和右端 路径中间的点)

如上图,每个点到根的异或和都是0,所以这些点对都满足要求。

对于(2,3),2号节点 右端及更右端的节点数应为其子树节点数为3,3号节点 左端及更左端的节点数同理为1;

对于(4,5)也同理。

所以很自然的,我们需要先dfs遍历整棵树,计算出每个点子树的节点数,令节点i的子树节点数为num[i]。

但是对于某些点对,右端及更右端的节点数 并不是端点的子树节点数。

如(2,4),对于2号节点,包含它的应为{2,1,3},对于4则仍然为其子树节点数,即此时左端还是子树节点数,右端则不是。

分析两种情况的区别,可以发现取决于右端点是不是左端点的祖先,即二者是否处在通往根节点的同一条链上。

首先是计算同链的情况:

dfs过程中已知当前点编号now和异或和s,假设当前点是两个端点中的左端,通过unordered_map进行计数,令ump[s]表示该条链 从根到now的父亲 中所有可作为now的右端的节点(即异或和也为s) 的 右端及更右端的节点数之和,则ans需要加上num[now]*ump[s]。

要计算出ump[s]我们还需要一个辅助变量tmp,它表示的是dfs进入v这个子节点时,now作为右端的 右端及更右端的节点数,每次进入子节点前更新tmp和ump[s],为了保证不同链不被影响,回溯的时候需要再减去加上的部分。

这样就算完了答案的同链部分。

接下来是不同链的部分:

同样是dfs,dfs过程中已知当前点编号now和异或和s,当前点可能是左端也可能是右端无所谓,ump[s]代表已经计算过的与now不同链且异或和为s的所有节点的子树节点数之和,要保证dfs过程中不计算到处于同链的端点,应该先计算,dfs结束前再更新ump[s]。

至此,这道题就做完了。

AC代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=1e5+5;
const int mod=1e9+7;
int n,u,num[maxn];
ll w,ans;
struct node{
	int v;
	ll w;
	node(int _v=0,ll _w=0):v(_v),w(_w){}
};
vector<node> G[maxn];
unordered_map<ll,int> ump;
int dfsnum(int now){
	num[now]++;
	for(node tt:G[now]) num[now]+=dfsnum(tt.v);
	return num[now];
}
int tmp=0;
void dfs(int now,ll s){//统计到根同链 ump[s]:当前点往上到根所有点右端点数和  tmp:单个点右端的点数
	ans=(ans+num[now]*1ll*ump[s])%mod;
	for(node tt:G[now]){
		tmp=(tmp+num[now]-num[tt.v])%mod;
		ump[s]=(ump[s]+tmp)%mod;
		dfs(tt.v,s^tt.w);
		ump[s]=(ump[s]-tmp)%mod;
		tmp=(tmp-num[now]+num[tt.v])%mod;
	}
}
void dfs1(int now,ll s){
	ans=(ans+num[now]*1ll*ump[s])%mod;
	for(node tt:G[now]) dfs1(tt.v,s^tt.w);
	ump[s]=(ump[s]+num[now])%mod;
}
int main(){
//	freopen("in.txt","r",stdin);
	scanf("%d",&n);
	for(int i=2;i<=n;i++){
		scanf("%d %lld",&u,&w);
		G[u].push_back(node(i,w));
	}
	dfsnum(1);//计算每个点子树节点数 
	dfs(1,0);//计算同链的 
	ump.clear();
	dfs1(1,0);//计算不同链的 
	cout<<(ans+mod)%mod;
    return 0;
}

 

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值