城市道路
挑战任务
A 国的国土由若干个城市组成,这些城市分布在若干个岛屿上,城市之间由一些 双向 道路连接,并且 互相联通 。如果一条道路跨越了两个岛屿,那么这条道路就是一座桥。
现在你有一张 A 国的地图,地图上标明了 A 国所有的城市和道路,但并未标明岛屿。
其中 A 国的桥满足了以下条件:如果将一条道路 删去之后,存在两个城市 不再联通,那么这条道路 一定是 一座桥。
现在,请你回答以下两个问题:
- A 国一共有多少座桥;
- 从任一城市到另外任一城市 最多 需要经过多少座桥。
题意
给出一张连通的无向图(有重边),求出所有的桥,和任意两点间桥的数量的最大值。
数据范围 :1<=n<=1e5 1<=m<=5e5
思路
求桥,tarjan算法肯定少不了,但是当我交几发后发现没那么简单,不是直接套个模板就能解决的。总的思路:首先Tarjan边双连通 缩点,再求桥,跑一边树的直径。O(n+m)。
缩点是为了去掉环,跑树的直径。
第一问求桥的数量,发现几个样例的答案都比我小,很纳闷,模板肯定是不会有问题的,思考还有什么要注意的点,只有这种可能,我的答案会偏大,当出现重边的时候,去点其中一条重边不会影响连通,题目肯定是这个意思,原模板是多条重边只算一条边的,所以答案会偏大。
如何改,归根是缩点不够彻底,没有把重边也缩成一个点。首先找出重边:if(child==fa) num++,num没有重边的情况一般是1,因为这是双向图,如果有重边自然>1。
再进行缩点,以前那个belong数组便不够用的,不好判断,因为重边可能出现在环中间,这个时候不用管,也有可能出现在桥上:
- 重边与重边相邻:所有重边都需要缩成一个点
- 重边与环相邻:重边与环缩成一个点
- 重边独立:这两点缩成一个点
原模板不够用,而且bcc在每找到一个环的时候会,也就是原模板的意思是每个环是一个缩点,下一个缩点一定不一样,bcc就会变化(bcc表示当前是属于哪一个缩点)
为解决这个问题考虑使用并查集,每找到一个环缩成一个点,重边也是,这样就解决所有的问题了。
缩点解决后,重新建图,跑一遍tarjan求所有的桥,树的直径。为什么最后求一遍树的直径就可以了呢,很简单,因为缩点后,剩下的是一颗树,每一条边都是桥(其实记录一下新图有多少点就可以知道桥有多少(n-1),不过也没事,影响不大),所以答案就是求树上两点最长距离,就是树的直径。
Tarjan复杂度都是O(n+m) 树的直径两遍bfs也是O(n+m),并查集复杂度O(
α
\alpha
αm),总复杂度是O(n+
α
\alpha
αm),并查集压缩路径
α
\alpha
α很小。
具体算法细节不解释,只提供思路。
代码:
#ifndef __SOLVER_H__
#define __SOLVER_H__
#include <bits/stdc++.h>
using namespace std;
#define maxn 100005
#define maxm 1000005
int head[maxn], num, tov[maxm], nex[maxm], len[maxn];
int ff[maxn];
bool instack[maxn];
int low[maxn], dfn[maxn];//时间戳
int sta[maxn], fa[maxn], fuben[maxn];//栈,记录父节点,记录用一个边双连通分量的所有点
int top, id, bcc;//栈顶,时间计数,边双连通分量的个数
class Solver
{
public:
//并查集
int find(int x)
{
return ff[x] == x ? x : ff[x] = find(ff[x]);
}
void add_edge(int from, int to, int dis)
{
nex[++num] = head[from];
tov[num] = to;
head[from] = num;
len[num] = dis;
}
//边双连通分量
void tarjan1(int u, int fa)
{
int i;
int num = 0;//记录重边
dfn[u] = low[u] = ++id;
instack[u] = 1;
sta[++top] = u;
for (i = head[u]; i != -1; i = nex[i])
{
int v = tov[i];
if (v == fa)
{
num++;
continue;
}
if (!dfn[v])
{
tarjan1(v, u);
low[u] = min(low[v], low[u]);
}
else if (instack[v])
low[u] = min(dfn[v], low[u]);
}
if (num>1)//存在重边
{
int x = find(u), y = find(fa);
if (x != y)
ff[x] = y;
}
if (dfn[u] == low[u])//对环缩点,如果是单点也不影响ff[u]=u;
{
++bcc;
int fu = find(u);
do
{
i = sta[top--];
int fv = find(i);
if (fu != fv)
ff[fv] = fu;
instack[i] = 0;
} while (i != u);
}
}
int ind, ans1, ans2;
void tarjan2(int cur, int fa)//cur是当前节点,fa是他的父亲
{
int child;
dfn[cur] = ++ind;//计算当前节点的时间戳
low[cur] = dfn[cur];//当前可以访问到的最早时间戳肯定是自己的时间戳
for (int i = head[cur]; i != -1; i = nex[i]) //遍历cur的所有出点
{
child = tov[i];
if (dfn[child] && child != fa)
low[cur] = min(low[cur], dfn[child]);//如果访问到了不是父亲节点的节点,更新low的值
if (!dfn[child])//如果这个节点之前没有被访问过
{
tarjan2(child, cur);//进行一次dfs过程
if (dfn[cur]<low[child])
ans1++;
low[cur] = min(low[cur], low[child]);//更新low的值
}
}
}
int dis[maxn], Max, t;
bool vis[maxn];
int bfs(int st)
{
Max = 0, t = st;
memset(dis, 0, sizeof(dis));
memset(vis, false, sizeof(vis));
queue<int>q;
q.push(st);
while (!q.empty())
{
int u = q.front();
vis[u] = true;
q.pop();
for (int i = head[u]; i != -1; i = nex[i])
{
int v = tov[i], di = len[i];
if (!vis[v])
{
dis[v] = dis[u] + di;
q.push(v);
if (dis[v]>Max)
{
Max = dis[v];
t = v;
}
}
}
}
}
int get_max(int st)//两遍bfs求树的直径
{
bfs(st);
bfs(t);
return Max;
}
void init1()
{
memset(head, -1, sizeof(head));
num = 0;
memset(instack, 0, sizeof(instack));
memset(low, 0, sizeof(low));
memset(dfn, 0, sizeof(dfn));
memset(sta, 0, sizeof(sta));
memset(fa, 0, sizeof(fa));
memset(fuben, 0, sizeof(fuben));
top = 0, id = 0, bcc = 0;
}
void init2()
{
memset(head, -1, sizeof(head));
num = 0;
memset(low, 0, sizeof(low));
memset(dfn, 0, sizeof(dfn));
ind = 0;
ans1 = 0, ans2 = 0;
memset(dis, 0, sizeof(dis));
memset(vis, 0, sizeof(vis));
}
public:
pair<int, int> solve(int n, const vector<pair<int, int> > &edges)
{
/********** Begin **********/
for (int i = 1; i <= n; i++)
ff[i] = i;
init1();
for (int i = 0; i<edges.size(); i++)
{
int u = edges[i].first, v = edges[i].second;
add_edge(u, v, 0);
add_edge(v, u, 0);
}
for (int i = 1; i <= n; i++)//缩点
if (!dfn[i])
tarjan1(i, i);
init2();
int st = 1;
for (int i = 0; i<edges.size(); i++)//重新建图
{
int u = edges[i].first, v = edges[i].second;
int fu = find(u), fv = find(v);
if (fu != fv)
{
st = fu;
add_edge(fu, fv, 1);
add_edge(fv, fu, 1);
}
}
for (int i = 1; i <= n; i++)//求桥,在前面直接记录点的个数也可以
if (!dfn[find(i)])
tarjan2(find(i), find(i));
ans2 = get_max(find(st));//求树的直径
pair<int, int>ans = make_pair(ans1, ans2);
return ans;
/********** End **********/
}
};
#endif
int main()
{
int n, m;
while (cin >> n >> m)
{
vector<pair<int, int> >edge;
for (int i = 0; i<m; i++)
{
int x, y;
cin >> x >> y;
edge.push_back(make_pair(x, y));
}
Solver sol;
pair<int, int> ans = sol.solve(n, edge);
cout << ans.first << " " << ans.second << endl;
}
return 0;
}