【洛谷P4630】铁人两项

题目

题目链接:https://www.luogu.com.cn/problem/P4630
比特镇的路网由 \(m\) 条双向道路连接的 \(n\) 个交叉路口组成。
最近,比特镇获得了一场铁人两项锦标赛的主办权。这场比赛共有两段赛程:选手先完成一段长跑赛程,然后骑自行车完成第二段赛程。
比赛的路线要按照如下方法规划:

  1. 先选择三个两两互不相同的路口 \(s, c\)\(f\),分别作为比赛的起点、切换点(运动员在长跑到达这个点后,骑自行车前往终点)、终点。
  2. 选择一条从 \(s\) 出发,经过 \(c\) 最终到达 \(f\) 的路径。考虑到安全因素,选择的路径经过同一个点至多一次。

在规划路径之前,镇长想请你帮忙计算,总共有多少种不同的选取 \(s, c\)\(f\) 的方案,使得在第 \(2\) 步中至少能设计出一条满足要求的路径。
\(n\leq 10^5,m\leq 2\times 10^5\)

思路

考虑如果我们枚举了起点和终点,那么有哪些点可以作为中间点。显然中间点一定需要满足在起点到终点的任意一条简单路径上即可。
在图上的问题不是很好做。我们发现如果一条路径经过了一个点双,那么这个点双里的点是都可以作为中间点的。所以我们考虑先 Tarjan 缩点,建出圆方树。问题转化到树上。
现在任意两点之间有且仅有一条路径,所以我们只需要知道他们路径之间点双的大小即可。所以我们可以给每一个方点一个权值,为这个点双的大小。然后统计路径上权值之和。
但是一个点会出现在两个点双内,所以我们可能会算重。所以我们给每一个圆点 \(-1\) 的权值,这样如果走了这个点就会减去相应被算重的部分。
时间复杂度 \(O(n)\)

代码

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

const int N=200010,M=400010;
int n,m,num;
ll ans,sum;
bool vis[N];

struct edge
{
	int next,to;
};

struct Tree
{
	int tot,head[N];
	ll val[N],siz[N];
	edge e[M];
	Tree()
	{
		memset(head,-1,sizeof(head));
		memset(val,-1,sizeof(val));
	}
	
	void add(int from,int to)
	{
		e[++tot]=(edge){head[from],to};
		head[from]=tot;
	}
	
	void dfs1(int x,int fa,ll dis)
	{
		vis[x]=1;
		if (x<=n)
			sum+=dis+val[x],siz[x]=1;
		for (int i=head[x];~i;i=e[i].next)
		{
			int v=e[i].to;
			if (v!=fa)
			{
				dfs1(v,x,dis+val[x]);
				siz[x]+=siz[v];
			}
		}
	}

	void dfs2(int x,int fa)
	{
		if (x<=n) ans+=sum;
		for (int i=head[x];~i;i=e[i].next)
		{
			int v=e[i].to;
			if (v!=fa)
			{
				sum=sum+val[v]*(num-siz[v])-val[x]*siz[v];
				dfs2(v,x);
				sum=sum-val[v]*(num-siz[v])+val[x]*siz[v];
			}
		}
	}
}T;

struct Tarjan
{
	int tot,cnt,head[N],dfn[N],low[N];
	edge e[M];
	stack<int> st;
	Tarjan() { memset(head,-1,sizeof(head)); }
	
	void add(int from,int to)
	{
		e[++tot]=(edge){head[from],to};
		head[from]=tot;
	}
	
	void tarjan(int x)
	{
		dfn[x]=low[x]=++tot;
		st.push(x);
		for (int i=head[x];~i;i=e[i].next)
		{
			int v=e[i].to;
			if (!dfn[v])
			{
				tarjan(v);
				low[x]=min(low[x],low[v]);
				if (low[v]>=dfn[x])
				{
					 int y,siz=1; cnt++;
					 do {
					 	y=st.top(); st.pop();
					 	T.add(cnt,y); T.add(y,cnt);
					 	siz++;
					 } while (y!=v);
					 T.add(cnt,x); T.add(x,cnt);
					 T.val[cnt]=siz;
				}
			}
			else low[x]=min(low[x],dfn[v]);
		}
	}
}G;

int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1,x,y;i<=m;i++)
	{
		scanf("%d%d",&x,&y);
		G.add(x,y); G.add(y,x);
	}
	G.tot=0; G.cnt=n;
	for (int i=1;i<=n;i++)
		if (!G.dfn[i])
		{
			while (G.st.size()) G.st.pop();
			G.tarjan(i);
		}
	for (int i=1;i<=n;i++)
		if (!vis[i])
		{
			sum=1;
			T.dfs1(i,0,0);
			num=T.siz[i];
			T.dfs2(i,0);
		}
	printf("%lld",ans);
	return 0;
}
posted @ 2021-01-14 09:23  stoorz  阅读(83)  评论(0编辑  收藏  举报