其他分享
首页 > 其他分享> > 快速题解to[APIO2018]Duathlon铁人两项 & 圆方树介绍

快速题解to[APIO2018]Duathlon铁人两项 & 圆方树介绍

作者:互联网

求不一定连通的简单无向图中,满足「存在一条路径 \(s\to f\) 经过 \(c\) 」的 \(\lang s,c,f\rang\) 的个数。\(\lang s,c,f\rang\) 和 \(\lang f,c,s\rang\) 算不同的元组。

点双的性质:对于一个点双连通分量中的两个点 \(u,v\),从 \(u\) 到 \(v\) 的所有路径的并为点双连通分量的点集。证明略。

当我们建立圆方树之后,求固定的 \(s\to f\) 的 \(c\) 的个数等于与「从圆点 \(s\) 到圆点 \(f\) 上的所有方点」相连的圆点的个数 \(-2\)。\(-2\) 是因为 \(s,f\) 不算。

遇到用圆方树统计路径数的问题,重要做法是给每一个方点和圆点赋权值。

显然,每个方点应赋值为其所代表的点双节点数。所以圆点赋值 \(-1\),以去重两个点双的公共部分。

这样,树上 \(s\to f\) 的点权值和就代表 \(c\) 的个数。注意,不需要再 \(-2\),道理显然。

问题转化为:

求树上所有有序圆点点对间路径点权和之和。

这是一个简单的问题,只需要用“统计贡献”的角度计算即可。由于文字不方便描述,见下面代码:

void dfs(int x,int p){
	vis[x]=1; //vis[x]标记x到达,不重要
	ll sum=0; //sum统计有多少组点对。在这里先不考虑顺序,因为s!=f所以最后答案*2即可。
    //第12行及以前sum统计的都是x子树内的路径条数
	for(int i=0;i<T[x].size();i++){
		int y=T[x][i];
		if(y==p)continue;
		dfs(y,x);
		sum+=1ll*siz[y]*siz[x]; //这里的siz[x]还是已经遍历过的儿子节点的子树中圆点个数的总和
		siz[x]+=siz[y];
	}
	if(x<=n)sum+=siz[x],siz[x]++; //x<=n代表x是圆点,那么由x连向
	sum+=1ll*siz[x]*(tmp-siz[x]); //由x子树内的圆点连向x子树外的圆点必经过x;tmp是连通块内节点个数(因为图不连通)
	f[x]*=sum; //f[x]会事先存住x的点权
	ans+=f[x];
}

代码:

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
typedef long long ll;
int n,m,t,dfc,cnt,tmp,stk[N],dfn[N],low[N],siz[N*2],vis[N*2];
ll ans,f[N*2];
vector<int>G[N],T[N*2];
void Tarjan(int x){
	stk[++t]=x;
	dfn[x]=low[x]=++dfc;
	for(int i=0;i<G[x].size();i++){
		int y=G[x][i];
		if(!dfn[y]){
			Tarjan(y);
			low[x]=min(low[x],low[y]);
			if(low[y]==dfn[x]){
				cnt++;
				while(t){
					T[cnt].push_back(stk[t]),T[stk[t]].push_back(cnt);
					f[cnt]++;
					if(stk[t--]==y)break;
				}
				T[cnt].push_back(x),T[x].push_back(cnt);
				f[cnt]++;
			}
		}
		else low[x]=min(low[x],dfn[y]);
	}
}
void dfs1(int x,int p){
	tmp+=x<=n;
	for(int i=0;i<T[x].size();i++){
		int y=T[x][i];
		if(y^p)dfs1(y,x);
	}
}
void dfs(int x,int p){
	vis[x]=1;
	ll sum=0;
	for(int i=0;i<T[x].size();i++){
		int y=T[x][i];
		if(y==p)continue;
		dfs(y,x);
		sum+=1ll*siz[y]*siz[x];
		siz[x]+=siz[y];
	}
	if(x<=n)sum+=siz[x],siz[x]++;
	sum+=1ll*siz[x]*(tmp-siz[x]);
	f[x]*=sum;
	ans+=f[x];
}
int main(){
	scanf("%d%d",&n,&m),cnt=n;
	for(int i=1,u,v;i<=m;i++)scanf("%d%d",&u,&v),G[u].push_back(v),G[v].push_back(u);
	for(int i=1;i<=n;i++)f[i]=-1;
	for(int i=1;i<=cnt;i++)if(!vis[i])Tarjan(i),tmp=0,dfs1(i,0),dfs(i,0);
	cout<<ans*2;
}

圆方树介绍

点双连通分量

我们都知道有向图的强连通分量叫 scc,无向图也有一种叫双连通分量的连通分量,分为点双(v-dcc)&边双(e-dcc)。

圆方树

对于一个无向连通图,求出每一个 v-dcc 后,为每一个 v-dcc 建立一个新点,将 v-dcc 内的所有点与该新点相连。则该新点为方点,原图中的点为圆点,由新边构成的新图将成为一颗树状结构,发明它的陈俊琨叫它圆方树。非连通图将构成森林。圆方树可以将图转为树,从而实现树上操作。

标签:APIO2018,点双,int,题解,连通,dcc,圆方树,分量
来源: https://www.cnblogs.com/impyl/p/15782447.html