您的位置:首页 > 其它

POJ 3352 Road Construction

2015-08-11 22:26 267 查看
题意就是至少加几条边,能够让原图没有桥。

1. 首先要找出图G的所有【边双连通分量】。

2、 把每一个【边双连通分量】都看做一个点(即【缩点】)

3、 问题再次被转化为“至少在缩点树上增加多少条树边,使得这棵树变为一个双连通图”。

首先知道一条等式:

若要使得任意一棵树,在增加若干条边后,变成一个双连通图,那么

至少增加的边数 =( 这棵树总度数为1的结点数 + 1 )/ 2

那么我们只需求缩点树中总度数为1的结点数(即叶子数)有多少就可以了。换而言之,我们只需求出所有缩点的度数,然后判断度数为1的缩点有几个,问题就解决了。

4、 求出所有缩点的度数的方法

两两枚举图G的直接连通的点,只要这两个点不在同一个【缩点】中,那么它们各自所在的【缩点】的度数都+1。注意由于图G时无向图,这样做会使得所有【缩点】的度数都是真实度数的2倍,必须除2后再判断叶子。

用了我的宇宙无敌超级大模版,1A了。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<vector>
#include<algorithm>
using namespace std;

const int maxn=10000+10; //结点数量
const int Maxn=2*100000+10; //边的数量
int low[maxn];
int dfn[maxn];
int U[maxn],V[maxn];//存初始边
int flag[maxn];//判断第i条边是不是割边
int iscut[maxn];//判断i结点是不是割点,去掉之后有几个连通分量
struct Edge
{
int from,to,id,ans;//ans为1,表示这条边是割边
} edge[Maxn];
vector<int>G[maxn];//邻接表
int N,M;//N个结点,M条边
int tmpdfn;//时间戳
int tot;
int son;
int Start,End;

//以下是输出点双连通分量用的
int top;
struct Printf_Egde
{
int u,v,id;
void output()
{printf("(%d,%d) ",u,v);}
};
Printf_Egde Stack[Maxn];
int Flag[Maxn];

int TxT[maxn];//求边双连通分量用的

//以下是缩点
vector<int>SD[maxn];
int FFLAG[maxn];
int JiHe[maxn];
int BianHao;
int RuDu[maxn];

void init()
{
for(int i=0; i<maxn; i++) G[i].clear();
for(int i=0; i<maxn; i++) SD[i].clear();
memset(JiHe,-1,sizeof(JiHe));
memset(low,0,sizeof(low));
memset(dfn,0,sizeof(dfn));
memset(iscut,0,sizeof(iscut));
memset(Flag,0,sizeof(Flag));
memset(flag,0,sizeof(flag));
memset(TxT,0,sizeof(TxT));
memset(FFLAG,0,sizeof(FFLAG));
memset(RuDu,0,sizeof(RuDu));
low[1]=dfn[1]=1;
tmpdfn=0;
tot=0;
son=0;
top=-1;

BianHao=1;
}

void AddEdge(int u,int v)
{
edge[tot].from=u;
edge[tot].to=v;
edge[tot].id=tot;
edge[tot].ans=0;
G[u].push_back(tot);
tot++;

edge[tot].from=v;
edge[tot].to=u;
edge[tot].id=tot;
edge[tot].ans=0;
G[v].push_back(tot);
tot++;
}

int Tarjan(int u,int id)
{
tmpdfn++;
int lowu=dfn[u]=tmpdfn;
for(int i=0; i<G[u].size(); i++)
{
int B=G[u][i];

Printf_Egde t;
if(!Flag[edge[B].id/2])//没有入过栈
{
Flag[edge[B].id/2]=1;
t.u=u;
t.v=edge[B].to;
t.id=edge[B].id/2;
Stack[++top]=t;
}
if(!dfn[edge[B].to])
{
int lowv=Tarjan(edge[B].to,edge[B].id);
lowu=min(lowu,lowv);
if(lowv>=dfn[u])
{
if(u!=1) iscut[u]++;
if(u==1) son++;

/*
//输出点双连通分量
printf("点双连通分量:");
while(1)
{
if(top==-1) break;
Printf_Egde t1;
t1=Stack[top];
t1.output();
top--;
if(t1.id==t.id) break;
}
printf("\n");
*/
//判断是不是割边
if(lowv>dfn[u])
edge[B].ans=1;
}
}
else if(dfn[edge[B].to])
{
if(edge[B].id/2==id/2) continue;
lowu=min(lowu,dfn[edge[B].to]);
}
}

low[u]=lowu;
return lowu;
}

void Display_Cutting_edge()
{
for(int i=0; i<2*M; i++)
if(edge[i].ans)
{
FFLAG[i]=1;
//printf("第%d条边是割边:(%d,%d)\n",edge[i].id/2,edge[i].from,edge[i].to);
}

}

void Display_Cutting_point()
{
if(son>1) iscut[1]=son-1;
for(int i=Start;i<=End;i++)
if(iscut[i]){}
//printf("编号为%d的结点是割点,删除后有%d个连通分量\n",i,iscut[i]+1);
}

void Dfs(int x,int y)
{
int XZ=0;
for(int i=0;i<G[x].size();i++)
{
int B=G[x][i];
if(!flag[edge[B].id/2])
{
XZ=1;
flag[edge[B].id/2]=1;
TxT[edge[B].to]=1;
// printf("(%d,%d) ",edge[B].from,edge[B].to);

if(JiHe[edge[B].from]==-1&&JiHe[edge[B].to]==-1)
{
JiHe[edge[B].from]=BianHao;
JiHe[edge[B].to]=BianHao;
BianHao++;
}
else if(JiHe[edge[B].from]!=-1)
JiHe[edge[B].to]=JiHe[edge[B].from];
else if(JiHe[edge[B].to]!=-1)
JiHe[edge[B].from]=JiHe[edge[B].to];

Dfs(edge[B].to,y+1);
}
}
if(!XZ&&!y)
{

//printf("(%d) ",x);
if(JiHe[x]==-1)
{
JiHe[x]=BianHao;
BianHao++;
}
}
}

void Slove()
{
//把桥都标为1
for(int i=0; i<2*M; i++)
if(edge[i].ans)
flag[edge[i].id/2]=1;

for(int i=Start;i<=End;i++)
{
if(!TxT[i])
{
TxT[i]=1;
// printf("边双连通分量:");
Dfs(i,0);
// printf("\n");
}
}
}

int main()
{
while(~scanf("%d%d",&N,&M)){
init();
for(int i=0; i<M; i++)
{
scanf("%d%d",&U[i],&V[i]);
AddEdge(U[i],V[i]);
}

//设置结点编号的起点和终点
Start=1;
End=N;

Tarjan(1,-1);

//割点的输出
Display_Cutting_point();

//割边的输出
Display_Cutting_edge();

//点双连通分量在Tarjan过程中已经输出了

//求边双连通分量,并输出
Slove();

for(int i=0;i<2*M;i++)
{
if(JiHe[edge[i].from]!=JiHe[edge[i].to])
{
RuDu[JiHe[edge[i].from]]++;
RuDu[JiHe[edge[i].to]]++;
}
}
int AAns=0;
for(int i=1;i<BianHao;i++)
{
RuDu[i]=RuDu[i]/2;
if(RuDu[i]==1) AAns++;
}
printf("%d\n",(AAns+1)/2);}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: