洛谷题单指南-连通性问题-P4630 [APIO2018] 铁人两项
原题链接:https://www.luogu.com.cn/problem/P4630
题意解读:在无向图中,选取在一条简单的不走重复点的路径上的s、c、f三个点,求方案数。
解题思路:
1、问题分析
如果固定两点s、f,此时的方案数就是s到f路径上的点的个数减2(除去s、f自身)。
普通的无向图不容易处理,而圆方树刚好有一个神奇的特性:如果方点权值为其点双连通分量大小、圆点权值为-1,那么两个圆点之间路径的点权值之和,正好就是原图中两个点所有简单路径上的点的个数减2。
基于这个特性,我们只需要对圆方树中的圆点枚举s、f,然后求路径权值之和。
直接枚举时间复杂度过高,换一个角度,枚举所有的点(圆点和方点),求有多少条经过该点的路径,然后该点对答案的贡献就是2权值路径数。
乘2是因为两个方向是不同方案;
对于经过一个点的路径数,在树上有两种可能:
经过u点的路径两端都在u的子树上,枚举u的子树,用子树v的大小乘以其余子树的大小,然后求和,即是经过u的路径数
经过u点的路径两端只有一个在u的子树上,用子树u的大小乘以剩余节点数,即使经过u的路径数
通过一次DFS即可求解。
2、算法流程**
构建圆方树,同时记录圆方树中节点个数
DFS每个点,求每个点对答案的贡献
注意图不一定连通
100分代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 100005, M = 200005;
int head1[N], head2[N * 2], to[M * 3], nxt[M * 3], idx; //head1:原图 head2:圆方树
int dfn[N], low[N], timestamp;
stack<int> stk;
int cnt; //点双的个数
int w[N * 2]; //圆方树的点权值,方点是点双的大小,圆点是-1
int sz[N * 2]; //圆方树中节点的子树大小
int total; //圆方树一个连通块的大小
long long ans;
int n, m;
void add(int head[], int a, int b)
{
to[++idx] = b;
nxt[idx] = head[a];
head[a] = idx;
}
void tarjan(int u, int p)
{
total++;
dfn[u] = low[u] = ++timestamp;
stk.push(u);
for(int i = head1[u]; i; i = nxt[i])
{
int v = to[i];
if(v == p) continue;
if(!dfn[v])
{
tarjan(v, u);
low[u] = min(low[u], low[v]);
if(low[v] >= dfn[u])
{
cnt++; //找到点双
add(head2, n + cnt, u), add(head2, u, n + cnt); //圆方树建边
w[u] = -1; //圆点权值
int size = 1;
while(true)
{
int t = stk.top(); stk.pop();
size++;
add(head2, n + cnt, t), add(head2, t, n + cnt); //圆方树建边
w[t] = -1; //圆点权值
if(t == v) break;
}
w[n + cnt] = size; //方点权值
}
}
else low[u] = min(low[u], dfn[v]);
}
}
void dfs(int u, int p)
{
if(u <= n) sz[u] = 1; //子树大小只计算圆点
for(int i = head2[u]; i; i = nxt[i])
{
int v = to[i];
if(v == p) continue;
dfs(v, u);
ans += 2ll * sz[v] * sz[u] * w[u]; //点u作为中点,s、f分别在子树中
sz[u] += sz[v];
}
ans += 2ll * sz[u] * (total - sz[u]) * w[u]; //点u作为中点,s、f一个在子树中
}
int main()
{
cin >> n >> m;
for(int i = 1;i <= m; i++)
{
int u, v;
cin >> u >> v;
add(head1, u, v), add(head1, v, u);
}
for(int i = 1; i <= n; i++)
{
if(!dfn[i])
{
total = 0;
tarjan(i, 0);
dfs(i, 0);
}
}
cout << ans;
return 0;
}