【DSU+思维】CF855G Harry Vs Voldemort

【题目】
原题地址
定义三元组 ( u , v , w ) (u,v,w) (u,v,w)为合法的当且仅当存在路径 u → w , v → w u\rightarrow w,v\rightarrow w uw,vw且两条路径没有边交。初始给定一棵 n n n个点的树, q q q次操作每次增加一条边,并询问当前图中有多少个合法三元组。
n , q ≤ 1 0 5 n,q\leq 10^5 n,q105

【解题思路】
如果只是一棵树的话,可以通过枚举中间点 w w w来计数。
如果是一个图的话,若 u , v , w u,v,w u,v,w存在于同一个边双中,则三元组 ( u , v , w ) (u,v,w) (u,v,w)必然满足条件。
对于不在同一个边双中的合法三元组,分两种情况,一种是中间点和一个端点在同一个边双中,这时另一个端点可以随便取;一种是三个点都不在同一个边双中,这样的话就跟一般的树是一样的。 于是
只要维护每个点作为 w w w的贡献,我们用 DSU \text{DSU} DSU来维护边双,同时维护答案即可。

更具体地,考虑怎样维护答案,假设我们现在合并了 S , T S,T S,T两个集合,那么显然要先减去由这两个集合中的点作为 w w w的贡献,有三种情况:

  • u , v u,v u,v均为 w w w所在边双中的点
  • u , v u,v u,v其中一个在 w w w所在边双内,令一个不在
  • u , v u,v u,v均不在 w w w所在边双中
    其中前两个我们可以简单用边双大小来维护。
    第三个直接统计并不是很方便,于是我们容斥一下,计算来自边双同一侧的 u , v u,v u,v有多少对,这个可以通过维护 f u f_u fu表示 u u u连通块中一个点能匹配到同侧的两个点有多少种方案,记 s u s_u su u u u连通块大小,则不合法方案为 w u × f u w_u\times f_u wu×fu。合并 w w w时,令 u u u v v v的父亲,如果我们直接 w u + w v w_u+w_v wu+wv显然会将 v v v所在子树内的点对以及不在 v v v子树中,但在 u u u子树中的点计算两次,这里再减掉贡献就可以了。

复杂度 O ( n α ( n ) ) O(n\alpha(n)) O(nα(n))

【参考代码】

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const ll N=2e5+10;
ll Q,n,tot,head[N];
ll dep[N],siz[N],sz[N];
ll f[N],fa[N],w[N];
ll ans;

ll read()
{
	ll ret=0;char c=getchar();
	while(!isdigit(c)) c=getchar();
	while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
	return ret;
}
void write(ll x){if(x>9) write((ll)x/10);putchar((x%10)^48);}
void writeln(ll x){write(x);puts("");}

struct Tway{ll v,nex;}e[N<<1];
void add(ll u,ll v)
{
	e[++tot]=(Tway){v,head[u]};head[u]=tot;
	e[++tot]=(Tway){u,head[v]};head[v]=tot;
}

ll sqr(ll x){return (ll)x*x;}

void dfs(ll x)
{
	siz[x]=1;dep[x]=dep[fa[x]]+1;
	for(ll i=head[x];i;i=e[i].nex)
	{
		ll v=e[i].v;
		if(v==fa[x]) continue;
		fa[v]=x;dfs(v);siz[x]+=siz[v];w[x]+=sqr(siz[v]);
	}
	ans-=(ll)(w[x]+=sqr(n-siz[x]));
}


ll findf(ll x){return f[x]==x?x:f[x]=findf(f[x]);}
ll get(ll x){return (ll)sz[x]*(sz[x]-1)*(sz[x]-2);}
void merge(ll x,ll y)
{
	ans-=(sqr(n-sz[x])-w[x])*sz[x];
	ans-=(sqr(n-sz[y])-w[y])*sz[y];
	//cerr<<ans<<endl;
	ans-=(n-sz[x])*sz[x]*(sz[x]-1)*2;
	ans-=(n-sz[y])*sz[y]*(sz[y]-1)*2;
	ans-=get(x)+get(y);f[y]=x;sz[x]+=sz[y];
	//cerr<<ans<<endl;
	w[x]+=w[y]-sqr(siz[y])-sqr(n-siz[y]);
	//cerr<<ans<<endl;
	ans+=(sqr(n-sz[x])-w[x])*sz[x]+get(x);
	//cerr<<ans<<" "<<sz[x]<<endl;
	ans+=(n-sz[x])*sz[x]*(sz[x]-1)*2;
	//cerr<<ans<<endl;
}


int main()
{
#ifndef ONLINE_JUDGE
	freopen("CF855G.in","r",stdin);
	freopen("CF855G.out","w",stdout);
#endif
	n=read();
	for(ll i=1;i<n;++i) add(read(),read());
	ans=n*(n-1)*(n-1);dfs(1);
	for(ll i=1;i<=n;++i) f[i]=i,sz[i]=1;
	
	Q=read();writeln(ans);
	while(Q--)
	{
		
		ll u=findf(read()),v=findf(read());
		while(u^v)
		{
			if(dep[u]<dep[v]) swap(u,v);
			ll t=findf(fa[u]);merge(t,u);u=t;
		} 
		writeln(ans);
	}

	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值