题意:有一个N个点的有向带权图,要求找若干个有向圈,使得每个点恰好属于一个圈。请输出满足以上条件的最小权和。
解法:有向圈?也就是每个点有唯一的后继。这是一个可逆命题,同样地,只要每个点都有唯一的后继,那么它们一定恰好属于一个圈。而“唯一”可以想到二分图匹配。把每个点拆成两个点,分别放在二分图的两边。两侧的点连的边就是原来的边的转化,另外再给源点和汇点分别连 n 条容量为1、费用为0的边。这样就保证了每个点有唯一的后继。再由于是要求所有点都属于一个圈,也就是完美匹配,就判断一下是否满流,流入汇点的总流量是否为 n 就行了。
1 #include<cstdio> 2 #include<cstdlib> 3 #include<cstring> 4 #include<iostream> 5 #include<queue> 6 using namespace std; 7 8 const int N=110,M=30010,NN=310,D=110,INF=(int)1e9; 9 int n,len; 10 int last[NN],vis[NN],id[NN],pre[NN],flow[NN],d[NN]; 11 struct edge{int y,fl,c,next;}e[M]; 12 queue<int> q; 13 14 int mmin(int x,int y) {return x<y?x:y;} 15 void ins(int x,int y,int fl,int c) 16 { 17 e[++len].y=y,e[len].fl=fl,e[len].c=c; 18 e[len].next=last[x],last[x]=len; 19 e[++len].y=x,e[len].fl=0,e[len].c=-c; 20 e[len].next=last[y],last[y]=len; 21 } 22 bool spfa(int st,int ed) 23 { 24 while (!q.empty()) q.pop(); 25 memset(vis,0,sizeof(vis)); 26 memset(d,63,sizeof(d)); 27 memset(pre,0,sizeof(pre)); 28 q.push(st); 29 d[st]=0,vis[st]=1,flow[st]=INF; 30 while (!q.empty()) 31 { 32 int x=q.front(); 33 q.pop(); vis[x]=0; 34 for (int i=last[x];i;i=e[i].next) 35 { 36 int y=e[i].y; 37 if (e[i].fl && d[x]+e[i].c<d[y]) 38 { 39 d[y]=d[x]+e[i].c; 40 flow[y]=mmin(flow[x],e[i].fl); 41 id[y]=i,pre[y]=x; 42 if (!vis[y]) q.push(y),vis[y]=1; 43 } 44 } 45 } 46 return pre[ed]; 47 } 48 int Max_flow(int st,int ed) 49 { 50 int sum=0,h=0; 51 while (spfa(st,ed)) 52 { 53 sum+=flow[ed]*d[ed]; 54 h+=flow[ed]; 55 for (int i=ed;i!=st;i=pre[i]) 56 { 57 e[id[i]].fl-=flow[ed]; 58 e[id[i]^1].fl+=flow[ed]; 59 } 60 } 61 if (h==n) return sum; 62 return 0; 63 } 64 int main() 65 { 66 while (1) 67 { 68 scanf("%d",&n); 69 if (!n) break; 70 int st=2*n+1,ed=2*n+2,x,y; 71 len=1; 72 memset(last,0,sizeof(last)); 73 for (int i=1;i<=n;i++) 74 { 75 ins(st,i,1,0),ins(n+i,ed,1,0); 76 while (1) 77 { 78 scanf("%d",&x); 79 if (!x) break; 80 scanf("%d",&y); 81 ins(i,n+x,1,y); 82 } 83 } 84 int ans=Max_flow(st,ed); 85 if (!ans) printf("N "); 86 else printf("%d ",ans); 87 } 88 return 0; 89 }