【APIO2018】Duathlon 铁人两项 【圆方树】

题意:给一张 n n n m m m 边的简单无向图,求有多少个三元组 ( s , c , f ) (s,c,f) (s,c,f) ,满足存在一条从 s s s f f f 经过 c c c 的简单路径。

n ≤ 1 0 5 , m ≤ 2 × 1 0 5 n\leq 10^5,m\leq 2\times 10^5 n105,m2×105

首先这个 “经过 c c c 的简单路径” ,即 c c c 取所有 s s s f f f 的简单路径的交集,就是能到达的所有点双的并集,是圆方树的标志。具体讲解可以参考 PR的博客

问题转换成了:求所有点对路径上的点双的并集的大小 − 2 -2 2 (起始点) 之和。

建出圆方树,方点权值为其度数 (即点双的大小),圆点权值为 − 1 -1 1 (点双边界的割点处被统计了两次,需要减掉;起始点本来就要减掉)

这样统计所有圆点路径上的权值之和,就可以做到 O ( n 2 ) O(n^2) O(n2)

考虑每个结点的贡献,计算子树大小(方点不算大小)瞎算一下就可以 O ( n ) O(n) O(n)

因为我的板子比较奇怪,需要把边去重,是 O ( n log ⁡ n ) O(n\log n) O(nlogn)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <vector>
#include <algorithm>
#define MAXN 100005
#define MAXM 400005
using namespace std;
inline int read()
{
	int ans=0;
	char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans;
}
struct edge{int u,v;}e[MAXM];
int head[MAXN],nxt[MAXM],cnt=1;
inline void addnode(int u,int v)
{
	e[++cnt]=(edge){u,v};
	nxt[cnt]=head[u];
	head[u]=cnt;
}
int n,m;
int dfn[MAXN],low[MAXN],tim;
int stk[MAXM],tp,vis[MAXM],bcc[MAXM],vcnt;
vector<int> rtt[MAXM];
void tarjan(int u)
{
	dfn[u]=low[u]=++tim;
	for (int i=head[u];i;i=nxt[i])
	{
		if (!vis[i>>1]&&!bcc[i>>1]) vis[(stk[++tp]=i)>>1]=1;
		if (!dfn[e[i].v])
		{
			tarjan(e[i].v);
			low[u]=min(low[u],low[e[i].v]);
			if (dfn[u]==low[e[i].v])
			{
				rtt[u].push_back(++vcnt);
				rtt[vcnt].push_back(u);
				while (vis[i>>1])
				{
					int t=stk[tp--];
					vis[t>>1]=0;
					rtt[bcc[t>>1]=vcnt].push_back(e[t].v);
//					rtt[e[t].v].push_back(vcnt);
				}
			}
		}
		else low[u]=min(low[u],dfn[e[i].v]);
	}
}
int val[MAXM],siz[MAXM];
typedef long long ll;
ll ans;
void dfs(int u,int f,int tot)
{
	siz[u]=(u<=n);
	vis[u]=1;
	for (int i=0;i<(int)rtt[u].size();i++)
	{
		int v=rtt[u][i];
		if (v!=f)
		{
			dfs(v,u,tot);
			ans+=(ll)siz[u]*siz[v]*val[u];
			siz[u]+=siz[v];
		}
	}
	ans+=(ll)siz[u]*(tot-siz[u])*val[u];
}
int main()
{
	n=read(),m=read();
	for (int i=1;i<=m;i++)
	{
		int u,v;
		u=read(),v=read();
		addnode(u,v),addnode(v,u);
	}
	int las=0;
	vcnt=n;
	for (int i=1;i<=n;i++) if (!dfn[i])	tarjan(i),siz[i]=tim-las,las=tim;
	for (int i=1;i<=vcnt;i++)
	{
		sort(rtt[i].begin(),rtt[i].end());
		rtt[i].erase(unique(rtt[i].begin(),rtt[i].end()),rtt[i].end());
	}
	for (int i=1;i<=n;i++) val[i]=-1;
	for (int i=n+1;i<=vcnt;i++) val[i]=(int)rtt[i].size();
	for (int i=1;i<=n;i++) if (!vis[i]) dfs(i,0,siz[i]);
	printf("%lld\n",2*ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值