大致题意:
为了保护放牧环境,避免牲畜过度啃咬同一个地方的草皮,牧场主决定利用不断迁移牲畜进行喂养的方法去保护牧草。然而牲畜在迁移过程中也会啃食路上的牧草,所以如果每次迁移都用同一条道路,那么该条道路同样会被啃咬过度而遭受破坏。
现在牧场主拥有F个农场,已知这些农场至少有一条路径连接起来(不一定是直接相连),但从某些农场去另外一些农场,至少有一条路可通行。为了保护道路上的牧草,农场主希望再建造若干条道路,使得每次迁移牲畜时,至少有2种迁移途径,避免重复走上次迁移的道路。已知当前有的R条道路,问农场主至少要新建造几条道路,才能满足要求?
一个有桥的连通图,如何把它通过加边变成边双连通图?方法为首先求出所有的桥,然后删除这些桥边,剩下的每个连通块都是一个双连通子图。把每个双连通子图收缩为一个顶点,再把桥边加回来,最后的这个图一定是一棵树,边连通度为1。
统计出树中度为1的节点的个数,即为叶节点的个数,记为leaf。则至少在树上添加(leaf+1)/2条边,就能使树达到边二连通,所以至少添加的边数就是(leaf+1)/2。具体方法为,首先把两个最近公共祖先最远的两个叶节点之间连接一条边,这样可以把这两个点到祖先的路径上所有点收缩到一起,因为一个形成的环一定是双连通的。然后再找两个最近公共祖先最远的两个叶节点,这样一对一对找完,恰好是(leaf+1)/2次,把所有点收缩到了一起。
在此注解一下:双连通分量判断是以弹栈为标准的……
注意一点 ///
这题的题意是保证了图是连通的,所以才有上面的计算公式,所以只要dfs一次,如果图不连通,可能要dfs多次,并且不是上面的计算公式(leaf+1)/2
ps:去重还是哈希一下比较好
代码:
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 1e3 + 5;
int low[maxn], dfn[maxn], id[maxn], bcc_cnt, dfs_cnt;
int deg[maxn], top, Stack[maxn], n, m;
vector v[maxn];
stack s;
void init()
{
memset(low, 0, sizeof(low));
memset(id, 0, sizeof(id));
memset(dfn, 0, sizeof(dfn));
memset(deg, 0, sizeof(deg));
bcc_cnt = dfs_cnt = top = 0;
for(int i = 0; i < maxn; i++)
v[i].clear();
}
void tarjan(int x, int f)
{
dfn[x] = low[x] = ++dfs_cnt;
Stack[top++] = x;
int k = 0;
for(int i = 0; i < v[x].size(); i++)
{
int to = v[x][i];
if(to == f && !k) //处理重边
{
k++;
continue;
}
if(!dfn[to])
{
tarjan(to, x);
low[x] = min(low[x], low[to]);
}
else if(!id[to])
low[x] = min(low[x], dfn[to]);
}
if(low[x] == dfn[x])
{
bcc_cnt++;
while(top > 0)
{
int u = Stack[--top];
id[u] = bcc_cnt;
if(u == x) break;
}
}
}
void bcc()
{
// for(int i = 1; i <= n ; i++)
// if(!dfn[i])
// tarjan(i);
tarjan(1, -1); //因为图是联通的,只要DFS一个点,全图都可以搜到
}
int main()
{
while(~scanf("%d%d", &n, &m))
{
init();
int x, y;
for(int i = 0; i < m; i++)
{
scanf("%d%d", &x, &y);
v[x].push_back(y);
v[y].push_back(x);
}
bcc();
for(int i = 1; i <= n; i++)
{
for(int j = 0; j < v[i].size(); j++)
{
int to = v[i][j];
if(id[to] != id[i])
{
deg[id[i]]++;
deg[id[to]]++;
}
}
}
int ans = 0;
for(int i = 1; i <= bcc_cnt; i++)
{
if(deg[i] == 2) ans++;
}
printf("%d\n",(ans+1)/2);
}
return 0;
}
#include
#include
#include
#include
#include
有向图的的情况比较简单只有一种强连通,重边和连向自己的边对于强连通都没有任何影响
无向图的双连通要分点双连通(biconnected)和边双连通(edge_biconnected),连向自己的边对于俩种双连通也没有任何影响,但是重边对点双连通没有影响,但是对于边双连通有影响,因为在求边双连通时,要求对于任意俩点至少存在两条“边不重复”的路径,所以这个时候表示图我们不能用vector了,而是用邻接表,添加边的时候我们要一次添加正反俩条边,而且要相互可以索引查找,类似网络流里的反向弧,这样在我们dfs求割边时要以边的下标作为标记,在访问一了一条边时,要把这条边和其反向边同时标记为访问,最后对所有的边进行遍历,发现low[e.v] < pre[e.u]时,同样要把正反俩条边标记成割边,最后在不经过桥的情况下dfs求出边双连通分量即可