题意:n个顶点,m条边(有重边),问增加一条边,使原来桥边减少最多
思路:求出图中的边双连通分量,缩点建新图,然后求出这颗树的直径,新加的边应该在直径的两边,这样才能使原图中桥最小。因为重边的原因,在dfs的时候,对于从u到v,在v也一定会有一条路径到u,加一个变量标记一下就可以了。 求树的直径的做法:随便找一个点找与它距离最远的点,然后从这个点出发再做一次,就是树的直径了
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<stack>
using namespace std;
const int maxn=200010+10;
const int INF = 100000000;
int ans;
int n,m;
int dfs_clock;//时钟,每访问一个节点增1
vector<int> G[maxn];//G[i]表示i节点邻接的所有节点
int pre[maxn];//pre[i]表示i节点被第一次访问到的时间戳,若pre[i]==0表示i还未被访问
int low[maxn];//low[i]表示i节点及其后代能通过反向边连回的最早的祖先的pre值
vector<int>NewG[maxn];
bool vis[maxn];
int dis[maxn];
int sccno[maxn];
int scc_cnt;
stack <int>S;
int p;
int bridge;
//求出以u为根节点(u在DFS树中的父节点是fa)的树的所有割顶和桥
//初始调用为dfs(root,-1);
void dfs(int u,int fa)
{
int flag = 0;
pre[u]=low[u]=++dfs_clock;
S.push(u);
for (int i = 0;i<G[u].size();i++)
{
int v = G[u][i];
if (v==fa && !flag)
{
flag++;
continue;
}
if (!pre[v])
{
dfs(v,u);
low[u]=min(low[u],low[v]);
if (low[v]>pre[u])
bridge++;
}
else
low[u]=min(low[u],pre[v]);
}
if (low[u]==pre[u])
{
scc_cnt++;
for (;;)
{
int x = S.top();
S.pop();
sccno[x]=scc_cnt;
if (x==u)
break;
}
}
}
void bfs(int s)
{
memset(vis,0,sizeof(vis));
memset(dis,0,sizeof(dis));
queue<int>q;
q.push(s);
vis[s]=1;
while (!q.empty())
{
int u = q.front();
q.pop();
for (int i = 0;i<NewG[u].size();i++)
{
int v = NewG[u][i];
if (vis[v])
continue;
vis[v]=1;
dis[v]=dis[u]+1;
if (ans < dis[v])
{
p=v;
ans = dis[v];
}
q.push(v);
}
}
}
void find_scc()
{
dfs_clock=scc_cnt=bridge=0;
memset(sccno,0,sizeof(sccno));
memset(pre,0,sizeof(pre));
for (int i = 1;i<=n;i++)
if (!pre[i])
dfs(i,-1);
}
int main()
{
while(scanf("%d%d",&n,&m)==2&&n)
{
for(int i=0;i<=n;i++) G[i].clear(),NewG[i].clear();
for(int i=0;i<m;i++)
{
int u,v,w;
scanf("%d%d",&u,&v);
G[u].push_back(v);
G[v].push_back(u);
}
find_scc();
for (int u=1;u<=n;u++)
{
for (int i = 0;i<G[u].size();i++)
{
int v = G[u][i];
if (sccno[u]!=sccno[v])
{
NewG[sccno[u]].push_back(sccno[v]);
NewG[sccno[v]].push_back(sccno[u]);
}
}
}
ans=0;
bfs(1);
bfs(p);
printf("%d\n",bridge-ans);
}
return 0;
}