CodeForces - 1000E We Need More Bosses(tarjan缩点、树上直径)
题目大意:
n点m边无向图,找到两个点s、t,使得s到t必须经过的边最多,求最多的必须经过边数。
思路:
题目关键在于对“必须经过的边”的理解,拿样例1来说
5 5 5
1 2 |
2 3 2
3 1 / \
4 1 3---1
5 2 |
4
在同一个双连通分量中,任意两点都至少有两条简单路径,所以一个双连通分量内的点都不是必须经过的边,自然想到使用tarjan缩点,缩点后得到的无向图无环而形成了一棵树,我们使用两次dfs求得树上直径即可得到答案。
Code:
#include <bits/stdc++.h>
using namespace std;
const int N = 300010;
// const int N = 10;
int dfn[N], low[N], co[N]/*缩点后每个点对应的标号*/, col/*缩点后还有几个点*/, num/*时间戳*/;
int st[N], top;
bool vis[N];
vector<int> G[N << 1], G2[N << 1];
int n, m;
int maxdis, maxv;
int min(int a, int b) { return a < b ? a : b; }
void tarjan(int x, int fa) {
dfn[x] = low[x] = ++num;
st[++top] = x;
vis[x] = 1;
for (int i = 0; i < G[x].size(); i++) {
int v = G[x][i];
if (v == fa) continue;
if (!dfn[v]) { //没访问过
tarjan(v, x);
low[x] = min(low[x], low[v]);
} else if (vis[v]) { //在栈中
low[x] = min(low[x], dfn[v]);
}
}
if (low[x] == dfn[x]) { //防止一个环遍历到一半就出栈了
col++;
while (1) {
co[st[top]] = col;
// printf("%d ", st[top]);
vis[st[top]] = 0;
if (st[top--] == x) break;
}
//printf ("\n") ;
}
}
//u:dfs的源点,f: u点的父节点,d2s:u点到源点的距离
void dfs(int u, int f, int d2s) {
if (maxdis < d2s){
maxdis = d2s;
maxv = u;
}
for (int i = 0; i < G2[u].size(); i++) {
int v = G2[u][i];
if (v == f) continue;
dfs(v, u ,d2s + 1);
}
}
int main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n >> m;
for (int i = 0; i < m; i++) {
int u, v; cin >> u >> v;
G[u].push_back(v);
G[v].push_back(u);
}
tarjan(1, 0);
for (int i = 1; i <= n; i++) { //缩点后遍历原图重新建图为G2
for (int j = 0; j < G[i].size(); j++) {
int v = G[i][j];
if (co[i] != co[v]) { //省去同一个环内的点的连边
G2[co[i]].push_back(co[v]);
}
}
}
//在缩点后的树上求最长距离
dfs(1, -1, 0);
maxdis = 0;
dfs(maxv, -1, 0);
cout << maxdis << endl;
return 0;
}