题目描述
The Romanian Ministry of Transport decides to upgrade the Romanian roads. Each road is bidirectional and directly connects two towns. No two towns are directly connected by more than one road. The existing road network ensures direct or indirect links between any two towns in Romania.
However, upgrading the roads implies closing, in turn, each road and performing the necessary repairs. During these operations, it is necessary to preserve the road network connectivity. In order to do so, the Minister decides that new roads should be built, so that no matter which single road is closed at any given moment (exactly one road at a time), the traffic between any given pair of towns should still be possible. Of course, the number of these newly added roads should be minimized. Furthermore, no new road may directly connect two directly connected towns.
Write a program that determines the minimum number of the additional roads to be built and the pairs of towns to be connected with them. If there are several optimal solutions, only one is required.
输入
Line
1 :N M
two integers, separated by a blank, representing respectively the number of the towns and the number of the roads. The towns are labelled from1 toN .
Lines2…M+1 :T1 T2
two integers separated by a blank, representing the two towns connected by a road.
输出
Line
1 :K
an integer, representing the minimum number of additional roads.
Lines2…K+1 :C1 C2
two integers separated by a blank, representing a pair of towns (cities) to be connected by a road.
The order of town pairs in the output file is irrelevant.
样例输入
4 3
1 2
2 3
2 4
样例输出
2
1 4
1 3
数据范围
3≤N≤2500,2≤M≤20000 .
这道题居然是2017年第一题……
这题一看应该是缩边双之后搞。原来只知道一个结论就是将一个图补成边双连通所需要的最小边数是缩点后度数为1节点数/2上取整。然而当时讲怎么证明忘了,怎么加边也忘了。于是缩完点就乱搞了一发,每次都随便找两个这样的点连上一条边。
这题学校oj上没有spj,写一个直接把这道题代码粘过去判整个图是不是边双。一交,wa了。一搜同济oj上有,然而不让交。然后这个CEOI就没有前面BOI做得好,官网已经挂了。peter_819说,是不是你spj没判重边啊,加完新边确实可能出重边。改了一下,还是wa。
这题老师是放在“图中的圈与块”里的,正好搜到了课件,一看知道了。
如果要缩点的话,必须保证缩完点之后不能产生新的度数为1的节点。
比如下面这个:
连接4-6或者7-8都可以,如果连接6-7或者4-8,那么产生的新的边双分量缩点后会成为新的度数为1的节点。
知道这个之后就可以知道缩点要缩完之后不产生新的度数为1的点。观察到若不想出现上面的情况,缩的两点的LCA子树中叶子节点必须超过两个,每次暴力寻找暴力修改即可。
最后只剩下两个叶子节点或者一个根一个叶子,连上就行了。
代码写的奇丑无比。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <set>
using namespace std;
//g:原图 g2:缩点后新图
struct edge{
int to;
edge *next;
edge(int t, edge *n):to(t), next(n){}
}*g[2510], *g2[2510];
//rep:每个边双分量中任意一个点编号 deg:边双分量在新图中的度
int n, m, fa[2510], dfn[2510], low[2510], T, stk[2510], h, bel[2510], P=1, rep[2510], deg[2510];
//siz:新图中某个点子树叶子个数 cnt:子树大小
int top[2510], son[2510], siz[2510], cnt[2510], dep[2510];
set<int> G[2510]; set<int>::iterator it;
bool v[2510];
inline int rd(){
int a=0; char c=getchar();
while(!isdigit(c)) c=getchar();
while(isdigit(c)) (a*=10)+=c-'0', c=getchar();
return a;
}
void addedge(int u, int v){
g[u]=new edge(v, g[u]);
g[v]=new edge(u, g[v]);
}
//Tarjan
void dfs(int p){
dfn[p]=low[p]=T++; v[p]=1; stk[h++]=p;
for(edge *t=g[p];t;t=t->next){
if(t->to==fa[p])
continue;
if(!v[t->to])
fa[t->to]=p, dfs(t->to);
low[p]=min(low[p], low[t->to]);
}
if(low[p]==dfn[p]){
int w;
do{
w=stk[--h]; bel[w]=P;
}while(w!=p);
rep[P++]=p;
}
}
//LCA的预处理
void dfs2(int p){
cnt[p]=1;
if(deg[p]==1){
siz[p]=1;
return;
}
for(edge *t=g2[p];t;t=t->next){
if(t->to==fa[p])
continue;
fa[t->to]=p; dep[t->to]=dep[p]+1;
dfs2(t->to);
siz[p]+=siz[t->to]; cnt[p]+=cnt[t->to];
if(cnt[t->to]>cnt[son[p]])
son[p]=t->to;
}
}
void dfs3(int p, int t){
top[p]=t;
if(son[p])
dfs3(son[p], t);
for(edge *t=g2[p];t;t=t->next)
if(t->to!=fa[p]&&t->to!=son[p])
dfs3(t->to, t->to);
}
int lca(int u, int v){
while(top[u]!=top[v]){
if(dep[top[u]]>dep[top[v]])
u=fa[top[u]];
else
v=fa[top[v]];
}
return dep[u]<dep[v]?u:v;
}
int main(){
n=rd(), m=rd();
for(int i=0;i<m;i++)
addedge(rd(), rd());
//Tarjan求边双
dfs(1);
for(int i=1;i<=n;i++)
for(edge *t=g[i];t;t=t->next)
if(bel[i]!=bel[t->to])
deg[bel[t->to]]++;
int ans1=0;
for(int i=1;i<P;i++)
if(deg[i]==1)
ans1++, stk[h++]=i;
printf("%d\n", (ans1+1)>>1);
if(ans1==1)
return 0;
//缩点
for(int i=1;i<=n;i++)
for(edge *t=g[i];t;t=t->next)
if(bel[i]!=bel[t->to])
G[bel[i]].insert(bel[t->to]);
memset(deg, 0, sizeof deg);
for(int i=1;i<P;i++)
for(it=G[i].begin();it!=G[i].end();it++)
g2[i]=new edge(*it, g2[i]), deg[*it]++;
memset(fa, 0, sizeof fa);
//把某个度数不为1节点提到根上
int root=0;
for(int i=1;i<P;i++)
if(deg[i]>1)
root=i;
dfs2(root); dfs3(root, root);
while(ans1>2){
bool f=0;
for(int i=1;!f&&i<P;i++)
//表示是新图的一个叶子节点 应该有更好的表示方式
if(cnt[i]==1&&siz[i]==1)
for(int j=i+1;!f&&j<P;j++)
if(cnt[j]==1&&siz[j]==1)
if(siz[lca(i, j)]>2){
int p;
printf("%d %d\n", rep[i], rep[j]);
//暴力修改
for(p=i;p!=root;p=fa[p])
siz[p]--, cnt[p]--;
for(p=j;p!=root;p=fa[p])
siz[p]--, cnt[p]--;
siz[p]-=2; ans1-=2; cnt[p]-=2;
f=1;
}
}
//剩下的点单独处理
if(ans1==2){
bool f=0;
for(int i=1;i<P;i++)
if(cnt[i]==1&&siz[i]==1){
if(!f)
printf("%d ", rep[i]), f=1;
else
printf("%d\n", rep[i]);
}
}
else if(ans1==1){
for(int i=1;i<P;i++)
if(cnt[i]==1&&siz[i]==1)
printf("%d %d\n", rep[root], rep[i]);
}
return 0;
}
这题还是不错的,需要灵活一点构造。就是写的太挫了。思考的时候还是要注意细节。
%%%Tarjan