F. Unique Occurrences--Educational Codeforces Round 129 (Rated for Div. 2)

这篇博客介绍了如何高效地解决一类树上路径问题,即计算所有点对之间路径上权值只出现一次的次数。文章提供了两种方法:一是通过深度优先搜索(DFS)计算每个连通块的大小并计算贡献;二是利用线性链剖(LCT)数据结构在O(nlogn)的时间复杂度内解决。这两种方法都涉及到了树的结构、连通块的划分以及权值对答案的贡献计算。
摘要由CSDN通过智能技术生成

题意

给定一棵树,每条边有权值, f ( v , u ) f(v,u) f(v,u) 表示 v v v u u u 路径之间仅出现过一次的权值的数量,计算对于所有的点对, f f f 的和。

分析

首先显然要将其转化为边对答案的贡献,考虑对于一个权值的所有边,其将树上的点分成了许多连通块,那么贡献就是相邻连通块的size乘积之和。

做法一:

考虑在DFS的过程中将各个连通块的size处理出来,将每个连通块深度最小的点当作这个连通块的表示点(假设为 u u u),那么我们按照DFS序,在其向下DFS时,DFS到合法的各链上的深度最小的点时,就可以更新 u u u 连通块的size(具体看代码)

那么对于一个权值的关键点,其又形成了一棵树,这个贡献显然就很好算了。

然后第二次DFS把答案算一下就行了。

注意对于每一个权值还需要建一个虚根。

Code:

inline int read()
{
	int x=0,f=1;char c=getchar();
	while(c<'0'||c>'9')
	{
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9')
	{
		x=x*10+(c-'0');
		c=getchar();
	}
	return x*f;
}
int n;int cnt;
int a[maxn],b[maxn];
int ver[maxn<<1],nxt[maxn<<1],val[maxn<<1],head[maxn];
vector<int> g[maxn];
int siz[maxn<<1],f[maxn<<1];
int ans;
inline void add(int x,int y,int z)
{
	cnt++;
	ver[cnt]=y;
	nxt[cnt]=head[x];
	val[cnt]=z;
	head[x]=cnt;
}
inline void dfs1(int x,int fa)
{
	siz[x]=1;
	for(int i=head[x];i;i=nxt[i])
	{
		if(ver[i]==fa) continue;
		g[val[i]].push_back(ver[i]);
		dfs1(ver[i],x);
		siz[x]+=siz[ver[i]];
		g[val[i]].pop_back();
		f[g[val[i]].back()]-=siz[ver[i]]; 
	}
	f[x]+=siz[x]; 
}
inline void dfs2(int x,int fa)
{
	for(int i=head[x];i;i=nxt[i])
	{
		if(ver[i]==fa) continue;
		g[val[i]].push_back(ver[i]);
		dfs2(ver[i],x);
		g[val[i]].pop_back();
		ans+=f[g[val[i]].back()]*f[ver[i]];
	}
}
int gcd(int a,int b){ return b==0?a:gcd(b,a%b); }
signed main()
{
	n=read();
	rep(i,1,n-1)
	{
		int x=read(),y=read(),z=read();
		add(x,y,z);add(y,x,z);
	}
	rep(i,1,n)
	{
		g[i].push_back(i+n);
		f[n+i]=n;
	}
	dfs1(1,0);
	dfs2(1,0);
	cout<<ans<<endl;
} 

做法二:

事实上,这道题可以用LCT以 O ( n l o g n ) O(nlogn) O(nlogn) 轻松解决。

对于每一个权值,将其所有的边全部cut掉,然后再将此权值的每条边连接的两个连通块size相乘贡献进答案,然后再将这些边link回来。

在有板子的情况下大概用时只需要1min写完。

主函数Code:

int n;
vector<pair<int,int> > edge[N];
int main()
{
	n=read();
	rep(i,1,n) pushup(i);
	rep(i,2,n)
	{
		int x,y,z;
		x=read(),y=read(),z=read();
		edge[z].pb({x,y});
		link(x,y);
	}
	ll ans=0;
	rep(i,1,n)
	{
		for(auto [x,y]:edge[i]) cut(x,y);
		for(auto [x,y]:edge[i]) ans+=get_size(x)*get_size(y);
		for(auto [x,y]:edge[i]) link(x,y);
	}
	cout<<ans<<endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值