POJ 3177 Redundant Paths题目链接:http://poj.org/problem?id=3177
POJ 3352 Road Construction题目链接:http://poj.org/problem?id=3352
这两题很好玩,能用同样的代码过这两题。而且POJ上的数据都很弱,即使网上80%的代码都是错的,也照样能ac。
题目的话明显就是求至少加入几条边能使图变成边双连通图。
---------------下面是转载自優YoU http://blog.csdn.net/lyy289065406/article/details/6762370,但是他的一部分思路错了,所以我改了下---------------------------------------------------
首先建立模型:
给定一个连通的无向图G,至少要添加几条边,才能使其变为双连通图。
模型很简单,正在施工的道路我们可以认为那条边被删除了。那么一个图G能够在删除任意一条边后,仍然是连通的,当且仅当图G至少为双连通的。
PS:不要问我为什么不是3-连通、4-连通...人家题目问“至少添加几条边”好不...
显然,当图G存在桥(割边)的时候,它必定不是双连通的。桥的两个端点必定分别属于图G的两个【边双连通分量】(注意不是点双连通分量),一旦删除了桥,这两个【边双连通分量】必定断开,图G就不连通了。但是如果在两个【边双连通分量】之间再添加一条边,桥就不再是桥了,这两个【边双连通分量】之间也就是双连通了。
那么如果图G有多个【边双连通分量】呢?至少应该添加多少条边,才能使得任意两个【边双连通分量】之间都是双连通(也就是图G是双连通的)?
这个问题就是本题的问题。要解决这个问题:
1、 首先要找出图G的所有【边双连通分量】。
2、 把每一个【边双连通分量】都看做一个点(即【缩点】)
也有人称【缩点】为【块】,都是一样的。其实缩点不是真的缩点,图G的点分类处理,就已经缩点了。
以样例1为例,样例1得到的图G为上左图,
3、 问题再次被转化为“至少在缩点树上增加多少条树边,使得这棵树变为一个双连通图”。
首先知道一条等式:
若要使得任意一棵树,在增加若干条边后,变成一个双连通图,那么
至少增加的边数 =( 这棵树总度数为1的结点数 + 1 )/ 2
(证明就不证明了,自己画几棵树比划一下就知道了)
那么我们只需求缩点树中总度数为1的结点数(即叶子数)有多少就可以了。换而言之,我们只需求出所有缩点的度数,然后判断度数为1的缩点有几个,问题就解决了。
4、 求出所有缩点的度数的方法
两两枚举图G的直接连通的点,只要这两个点不在同一个【缩点】中,那么它们各自所在的【缩点】的度数都+1。注意由于图G时无向图,这样做会使得所有【缩点】的度数都是真实度数的2倍,必须除2后再判断叶子。
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
注意原博客中求边的双联通分量的时候,用的是tarjan算法中的low[]数组。他认为low[]数组相同的就是在同一个连通分量。事实证明是错的。因为对于
用例:
11 14
1 2
1 3
1 4
2 5
6 11
2 6
5 6
5 11
3 7
3 8
7 8
4 9
4 10
9 10
他的程序中给出的代码跑出来的结果是1,其实结果应该是2,其中low[11]用tarjan求的话应该等于3,low[2] = low[5] = low[6] = 2,但事实上点11和点2,5,6是同一个边连通分量中的。
既然不能用low[]的话我们就可以用下面这种方法:
1,。求出所有的割边(桥),
2.删去所有的割边后用dfs计算共有几个连通分量,其中每个连通分量重新缩为
3。记录缩点后的每个点的度数,然后按照上面的方法求。
#include<stdio.h>
#include<string.h>
#include<vector>
#include<stdlib.h>
#include<algorithm>
#define MEM(x,y) memset(x,y,sizeof(x))
#define INF 5100
using namespace std;
struct edge
{
int to;
int flag; //标记该边是不是割边(桥)
int id; //存反向边在边集中的下标
}e[2*INF];
int low[INF],deep[INF];
vector<int>graph[INF]; //graph[i]存以i为起点的边在在边集e[]中的下标
int dfs_deep,F,R,cnt;
int du[INF]; //用来存储点的度数
int vis[INF]; //用来存在dfs是否已经遍历过该点,以及该点后来缩点后所在的新的替代点
int lcount; //记录删去桥后连通分支的个数
bool map[INF][INF]; //用来判断是否有重边
void addedge(int a,int b) //加边
{
map[a][b] = true;
map[b][a] = true;
e[cnt].to = b;
e[cnt].id = cnt+1;
graph[a].push_back(cnt);
cnt++;
e[cnt].to = a;
graph[b].push_back(cnt);
e[cnt].id = cnt-1;
cnt++;
}
void tarjan(int node,int prenode) //求割边
{
low[node] = deep[node] = ++dfs_deep;
int len = graph[node].size();
for(int i = 0 ; i < len ; i ++)
{
int temp = graph[node][i]; //i为起点的边在e[]中的下标
if(!deep[e[temp].to])
{
tarjan(e[temp].to,node);
low[node] = min(low[node],low[e[temp].to]);
if(low[e[temp].to] > deep[node]) //求割边,与求割点有所不同,没有等于号
{
e[temp].flag = 1;
e[e[temp].id].flag = 1;
}
}
else
if(e[temp].to != prenode) //如果不是父子边(树边)
low[node] = min(low[node],deep[e[temp].to]);
}
}
void dfs(int node) //用来求当删除割边的时候,图中的连通分支
{
vis[node] = lcount;
int len = graph[node].size();
for(int i = 0 ; i < len ; i ++)
{
int temp = graph[node][i];
if(vis[e[temp].to] == 0 && e[temp].flag == 0) //没遍历过且不是割边
dfs(e[temp].to);
}
}
int main()
{
scanf("%d%d",&F,&R);
MEM(low,0);
MEM(deep,0);
MEM(du,0);
MEM(e,0);
MEM(vis,0);
MEM(map,0);
cnt = 0;
for(int i = 0 ; i <= F; i ++)
graph[i].clear();
while(R--)
{
int a,b;
scanf("%d%d",&a,&b);
if(map[a][b] == false && map[b][a] == false)
addedge(a,b);
}
dfs_deep = 0;
tarjan(1,0);
lcount = 1; //lcount存的是缩环后的替代点
for(int i = 1 ;i <= F ; i ++)
{
if(vis[i] == 0)
{
dfs(i);
lcount++;
}
}
for(int i = 1; i <= F; i ++)
{
int len = graph[i].size();
for(int j = 0 ; j < len ; j ++)
{
int temp = graph[i][j];
if(vis[i] != vis[e[temp].to]) //如果不是在同一个替代点,即不是在一个边连通分量内
{
du[vis[i]]++; //替代点的度数加
du[vis[e[temp].to] ] ++;
}
}
}
int ans = 0;
for(int i = 1; i < lcount; i ++)
{
du[i] = du[i]/2;
if(du[i] == 1)
ans++;
}
printf("%d\n",(ans+1)/2);
return 0;
}