【洛谷P4630】铁人两项
题目
题目链接:https://www.luogu.com.cn/problem/P4630
比特镇的路网由 \(m\) 条双向道路连接的 \(n\) 个交叉路口组成。
最近,比特镇获得了一场铁人两项锦标赛的主办权。这场比赛共有两段赛程:选手先完成一段长跑赛程,然后骑自行车完成第二段赛程。
比赛的路线要按照如下方法规划:
- 先选择三个两两互不相同的路口 \(s, c\) 和 \(f\),分别作为比赛的起点、切换点(运动员在长跑到达这个点后,骑自行车前往终点)、终点。
- 选择一条从 \(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;
}