2019 西安邀请赛 J. And And And(dfs处理同链贡献和不同链贡献)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


现场赛时看到4个求和就直接跳过了,其实并不难,没有太大的思维量
(但我不会写这个dfs,等于白给)


题解:预处理每个点到根节点的路径异或和,记为 v a l [ u ] val[u] val[u],两个点对答案有贡献时这个点的 v a l val val相等。
v a l [ u ] = v a l [ v ] val[u] = val[v] val[u]=val[v],那么 u , v u,v u,v 对总答案的贡献就等于经过 u , v u,v u,v 的路径数
根据 u , v u,v u,v 所在位置可以分为两种情况:
1. u , v u,v u,v同链,即 l c a ( u , v ) = u lca(u,v) = u lca(u,v)=u || l c a ( u , v ) = v lca(u,v) = v lca(u,v)=v
2. u , v u,v u,v不同链
对于第二种情况, u , v u,v u,v对答案的贡献就等于 s z [ u ] ∗ s z [ v ] sz[u] * sz[v] sz[u]sz[v] s z [ u ] sz[u] sz[u] u u u 点的子树大小
对于第一种情况,假设 u u u v v v 的祖先, s s s u u u -> v v v 路径上的一个儿子,则 u , v u,v u,v对答案的贡献为 ( n − s z [ s ] ) ∗ s z [ v ] (n - sz[s]) * sz[v] (nsz[s])sz[v]

d f s dfs dfs是沿着一条链遍历下去,基于 d f s dfs dfs的过程,可以将上述两种贡献分开计算:
1.当计算同链贡献时,只维护当前 d f s dfs dfs点到根节点这条链的结点信息,退出访问时情况这个点的信息,这样保证链上的点的贡献只来自于这条链。
2.当计算不同链贡献时,只有当前点退出 d f s dfs dfs时才维护这个点的信息,这样保证链上的点的贡献不来自于这条链。

具体看代码


代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
const int mod = 1e9 + 7;
#define pii pair<ll,ll>
#define fir first
#define sec second
typedef long long ll;
map<ll,ll> mp;
vector<pii> g[maxn];
int n,sz[maxn];
ll val[maxn];
ll ans = 0;
void prework(int u,int fa) {
	if(u == 1) val[u] = 0;
	sz[u] = 1;
	for(auto it : g[u]) {
		if(it.fir == fa) continue;
		val[it.fir] = val[u] ^ it.sec;
		prework(it.fir,u);
		sz[u] += sz[it.fir];
	}
}
void dfs1(int u,int fa) {				//计算同链贡献
	ans = (ans + 1ll * mp[val[u]] * sz[u] % mod) % mod; 
	for(auto it : g[u]) {
		if(it.fir == fa) continue;
		mp[val[u]] = (mp[val[u]] + n - sz[it.fir] + mod) % mod;
		dfs1(it.fir,u);
		mp[val[u]] = (mp[val[u]] - n + sz[it.fir] + mod) % mod;	
	}
}
void dfs2(int u,int fa) {				//计算不同链的贡献 
	ans = (ans + 1ll * mp[val[u]] * sz[u] % mod) % mod;
	for(auto it : g[u]) {
		if(it.fir == fa) continue;
		dfs2(it.fir,u);
	}
	mp[val[u]] = (mp[val[u]] + sz[u]) % mod;
}
int main() {
	scanf("%d",&n);
	for(int i = 2; i <= n; i++) {
		ll u,w;scanf("%lld%lld",&u,&w);
		g[u].push_back(pii(i,w));
		g[i].push_back(pii(u,w));
	}
	prework(1,0);
	dfs1(1,0);
	mp.clear();
	dfs2(1,0);
	printf("%lld\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值