题意:
给定现有的R条直接连接2个牧场的路,F-1<=R<=10,000,计算至少需要新修多少条直接连接2个牧场的道路,使得任何2个牧场之间至少有2条独立的路。
分析:
见代码及注释……
《图论算法理论实现应用》——P412
(啊,今天是过3177后的第三天晚上==!)
接着做练习题发现3177和3352是一模一样的……只是数据范围不同罢了。
/* POJ 3177; *边双连通分量里所有点可等价地看作一个点进行缩点——缩点后解决“在新图的树中至少添加多少条边能使图变为边双连通图”——添加边数=(书中度为1的结点数+1)/2. *采用并查集缩点,缩点后统计得到的树中叶子结点的个数,然后求应添加的边数 */ #include<iostream> #include<cstdio> #include<cstring> #define clr(a) memset(a,0,sizeof(a)) using namespace std; const int N=1005,M=20005; int n,m,w;//顶点数;边数;原图边双连通分量个数 int belong[N],low[N],dfn[N],visited[N];// ;顶点i可达祖先的最小编号;深度优先数;0未访问,1已访问,2已访问且已检查邻接顶点 int bridge[M][2],nbridge; struct Node{ int j;//另一顶点序号 Node *next;//下一个边结点 }; Node mem[M];int memp;//储存边结点的数组;数组中的序号 Node *e[N];//邻接表 int MIN(int a,int b){ return a>b?b:a; } /*邻接表中插入边(i,j)*/ void addEdge(Node *e[],int i,int j){ Node *p=&mem[memp++]; p->j=j; p->next=e[i]; e[i]=p; } int FindSet(int f[],int i){ int j=i,t; while(f[j]!=j) j=f[j]; while(f[i]!=i){ t=f[i]; f[i]=j; i=t; } return j;//最后进行缩点的点 } void UniteSet(int f[],int i,int j){ int p=FindSet(f,i),q=FindSet(f,j); if(p!=q) f[p]=q; } /*搜索双连通分量——顶点,?,深度,并查集数组*/ void dfs_2conn(int i,int father,int dth,int f[]){ int j,tofather=0; Node *p; visited[i]=1; low[i]=dfn[i]=dth; for(p=e[i];p!=NULL;p=p->next){ j=p->j; if(visited[j]==1 && (j!=father||tofather)) low[i]=MIN(low[i],dfn[j]); if(visited[j]==0){ dfs_2conn(j,i,dth+1,f); low[i]=MIN(low[i],low[j]); if(low[j]<=dfn[i])//i,j在同一个双连通分量 则利用UnionSet缩点 UniteSet(f,i,j); if(low[j]>dfn[i]){//(i,j)是桥 bridge[nbridge][0]=i; bridge[nbridge++][1]=j; } } if(j==father) tofather=1; } visited[i]=2; } /*求无向图极大边双连通分量的个数*/ int DoubleConn(){ int i,k,f[N],ncon=0; for(i=0;i<n;i++){ f[i]=i;//并查集数组 belong[i]=-1; } clr(visited); nbridge=0; dfs_2conn(0,-1,1,f); for(i=0;i<n;i++){ k=FindSet(f,i); if(belong[k]==-1) belong[k]=ncon++; belong[i]=belong[k]; } return ncon; } int main(){ int i,j,k; while(~scanf("%d%d",&n,&m)){ memp=0; clr(e); for(k=0;k<m;k++){ scanf("%d%d",&i,&j); i--;j--; addEdge(e,i,j);addEdge(e,j,i);//相互地,故(i,j),(j,i)都插入链表 } w=DoubleConn(); int d[N]={0};//收缩后各顶点度数 for(k=0;k<nbridge;k++){ i=bridge[k][0];j=bridge[k][1]; d[belong[i]]++;d[belong[j]]++; } int cnt=0;//缩点以后 叶子结点个数 for(i=0;i<w;i++) if(d[i]==1) cnt++; printf("%d\n",(cnt+1)/2); } return 0; }