CF1000E We Need More Bosses
题目描述
题目大意:
给定一个 n n n 个点 m m m 条边的无向图,保证图连通。找到两个点 s , t s,t s,t,使得 s s s到 t t t必须 经过的边最多(一条边无论走哪条路线都经过ta,这条边就是必须经过的边), 2 < = n < = 3 ∗ 1 0 5 , 1 < = m < = 3 ∗ 1 0 5 2<=n<=3*10^5,1<=m<=3*10^5 2<=n<=3∗105,1<=m<=3∗105
输入格式
第一行两个整数, n , m n,m n,m
以下m行,每行两个整数 x , y x,y x,y,表示 x y xy xy间有一条无向边相连
输出格式
一个整数,即最多的必须经过边数
感谢@守望 提供翻译
输入输出样例 #1
输入 #1
5 5
1 2
2 3
3 1
4 1
5 2
输出 #1
2
输入输出样例 #2
输入 #2
4 3
1 2
4 3
3 2
输出 #2
3
思路:
求下边的双连通性,然后找一条必经的最长路(题目中写的 s s s 到 t t t 必须经过的边最多 ),也就是求所有桥的数量。
最长路就是缩点后树的直径,所有的边都是桥,求一下桥的数量就是最多的必须经过边数。
T a r j a n Tarjan Tarjan 算法求 E D C C EDCC EDCC,套模板
cpp
inline void add(int a, int b) {
e.push_back({b, h[a]});
h[a] = e.size() - 1;
}
void Tarjan(int u, int in_edge) {
low[u] = dfn[u] = ++tot;
stk.push(u);
for(int i = h[u]; ~i; i = e[i].ne) {
int v = e[i].v;
if(!dfn[v]) {
Tarjan(v, i);
low[u] = std::min(low[u], low[v]);
if(low[v] > dfn[u]) bri[i] = bri[i ^ 1] = true; // 下个点的最小时间戳比当前点的时间戳大, 说明是桥
}else if(i != (in_edge ^ 1)) low[u] = std::min(low[u], dfn[v]); // 这条边不能是反向边
}
if(low[u] == dfn[u]) {
int v;
cnt++;
do{
v = stk.top();
stk.pop();
dcc[v] = cnt;
}while(v != u);
}
}
建立新的树:
cpp
tree.resize(cnt + 1);
Rep(u, 1, n + 1) {
for(int i = h[u]; ~i; i = e[i].ne) {
int v = e[i].v;
if(bri[i]) { // 这条边是桥
int nu = dcc[u], nv = dcc[v];
if(nu == nv) continue; // 不能连接相同编号
tree[nu].push_back(nv);
tree[nv].push_back(nu);
}
}
}
树的直径的求法:两段BFS,一段取任意一个点找到一个距离他最远的那个点 u u u ,然后在从 u u u 开始找,找到距离 u u u 最远的点 v v v ,这两个点之间的路径就是树的直径,也就是最长的距离 s − > t s -> t s−>t。
cpp
inline int BFS(int root) {
dist.assign(cnt + 1, INF);
q = std::queue<int>();
int max_num = root, max_dist = 0;
q.push(root);
dist[root] = 0;
while(!q.empty()) {
int u = q.front();
q.pop();
for(const int& v : tree[u]) {
if(dist[v] == INF) { // 没走过
dist[v] = dist[u] + 1;
if(dist[v] > max_dist) {
max_dist = dist[v];
max_num = v;
}
q.push(v);
}
}
}
return max_num;
}
main:
// 计算树的直径
int u = BFS(1); // 这个点是任意的
int v = BFS(u);
然后找一下从 u u u 到 v v v 一共经过了多少边就是多少桥。
cpp
inline void BFS(int u, int v) { // 重载
dist.assign(cnt + 1, INF);
q = std::queue<int>();
q.push(u);
dist[u] = 0;
while(!q.empty()) {
int now = q.front();
q.pop();
for(const int& ne : tree[now]) {
if(dist[ne] == INF) {
dist[ne] = dist[now] + 1;
q.push(ne);
}
}
}
std::cout << dist[v] << '\n';
}
T a r j a n + 树的直径 Tarjan + 树的直径 Tarjan+树的直径 A C c o d e : AC code: ACcode:
cpp
#include <iostream>
#include <climits>
#include <limits>
#include <vector>
#include <queue>
#include <stack>
typedef unsigned long long ull;
typedef long long ll;
typedef long double ld;
typedef std::pair<int, int> PII;
#define rep(i, n) for(int i = 0; i < n; i++)
#define Rep(i, len, n) for(int i = len; i < n; i++)
#define MAX_INT 0x7fffffff
#define MIN_INT 0x80000000
const int INF = std::numeric_limits<int>::max();
struct edge {
int v, ne;
};
int n, m, tot = 0, cnt = 0;
std::vector<edge> e;
std::vector<std::vector<int>> tree;
std::vector<int> low, dfn, bri, h, dcc, dist;
std::stack<int> stk;
std::queue<int> q;
inline void add(int a, int b) {
e.push_back({b, h[a]});
h[a] = e.size() - 1;
}
void Tarjan(int u, int in_edge) {
low[u] = dfn[u] = ++tot;
stk.push(u);
for(int i = h[u]; ~i; i = e[i].ne) {
int v = e[i].v;
if(!dfn[v]) {
Tarjan(v, i);
low[u] = std::min(low[u], low[v]);
if(low[v] > dfn[u]) bri[i] = bri[i ^ 1] = true;
}else if(i != (in_edge ^ 1)) low[u] = std::min(low[u], dfn[v]);
}
if(low[u] == dfn[u]) {
int v;
cnt++;
do{
v = stk.top();
stk.pop();
dcc[v] = cnt;
}while(v != u);
}
}
inline int BFS(int root) {
dist.assign(cnt + 1, INF);
q = std::queue<int>();
int max_num = root, max_dist = 0;
q.push(root);
dist[root] = 0;
while(!q.empty()) {
int u = q.front();
q.pop();
for(const int& v : tree[u]) {
if(dist[v] == INF) { // 没走过
dist[v] = dist[u] + 1;
if(dist[v] > max_dist) {
max_dist = dist[v];
max_num = v;
}
q.push(v);
}
}
}
return max_num;
}
inline void BFS(int u, int v) { // 重载
dist.assign(cnt + 1, INF);
q = std::queue<int>();
q.push(u);
dist[u] = 0;
while(!q.empty()) {
int now = q.front();
q.pop();
for(const int& ne : tree[now]) {
if(dist[ne] == INF) {
dist[ne] = dist[now] + 1;
q.push(ne);
}
}
}
std::cout << dist[v] << '\n';
}
int main(void) {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr), std::cout.tie(nullptr);
std::cin >> n >> m;
low.resize(n + 1);
dfn.resize(n + 1);
dcc.resize(n + 1);
h.resize(n + 1, -1);
bri.resize(2 * m + 1, false);
rep(i, m) {
int x, y;
std::cin >> x >> y;
add(x, y);
add(y, x);
}
Rep(i, 1, n + 1) if(!dfn[i]) Tarjan(i, -1);
if(cnt <= 1) {
std::cout << 0 << '\n';
return 0;
}
tree.resize(cnt + 1);
Rep(u, 1, n + 1) {
for(int i = h[u]; ~i; i = e[i].ne) {
int v = e[i].v;
if(bri[i]) {
int nu = dcc[u], nv = dcc[v];
if(nu == nv) continue;
tree[nu].push_back(nv);
tree[nv].push_back(nu);
}
}
}
// 计算树的直径
int u = BFS(1);
int v = BFS(u);
BFS(u, v);
return 0;
}
一些问题:
Q:为什么 u 到 v 是必经过的最长路径(树的直径)?
A:
代码中通过两次 BFS 来求树的直径。首先从任意一个点(这里是 1)开始进行 BFS,找到离这个点最远的点 u。然后从 u 点再次进行 BFS,找到离 u 最远的点 v。
根据树的直径的性质,对于一棵树,从任意一个点出发找到的最远点一定是直径的一个端点。所以第一次 BFS 找到的 u 是直径的一个端点。然后从 u 出发再次进行 BFS 找到的 v 就是直径的另一个端点。
这样 u 到 v 的路径就是这棵树的直径,因为树的直径定义为树中任意两点间距离的最大值,而通过上述两次 BFS 的方法保证了找到的 u 和 v 之间的路径是树中最长的路径。
Q:在原图中的意义(边双连通分量的角度)?
A:在原图中,边双连通分量内部是没有桥的,而桥连接了不同的边双连通分量。通过求缩点后树的直径,实际上是找到了原图中通过桥连接的两个最远的边双连通分量。u 到 v 的路径上经过的桥是原图中连接不同边双连通分量的关键路径。
Q:树的直径(u 到 v 的路径)在原图中的意义是:
A:最长的桥路径:直径路径上的边都是原图中的桥,这条路径是原图中通过桥连接的最长路径。
必经的关键路径:在原图中,如果要从一个边双连通分量到达另一个边双连通分量,必须通过它们之间的桥。直径路径代表了原图中最远的两个边双连通分量之间的必经之路。
Q:为什么这就是问题的解?
A:直径路径上的桥是原图中连接不同边双连通分量的关键路径。
任何跨越多个边双连通分量的路径都必须经过这些桥。因此,树的直径长度就代表了原图中两个最远的边双连通分量之间的最短路径长度(因为每次跨越一个桥算一步)。