二分图相关 小结
二分图匹配这一块的一大堆概念各种绕,我被虐爽了。。。
特别是其中各种各样的最大——最小关系,互补关系之间的转化等等。。。各种虐心。。。
先推荐一篇很长的文章,讲得比较详细,把几乎所有的问题都涉及到了。
http://dsqiu.iteye.com/blog/1689505
首先是跟最大匹配有关的问题一大堆:
给一个n*n的矩阵,上面有k颗小行星,你有一把枪,每次可以打一行或者一列,求最少打几次可以把小行星都蒸发掉(什么乱七八糟的。。。)
把行和列看成点,小行星看成连接所在行与列的边,就成了最小点覆盖,等于最大匹配数。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstdio> 2 #include<cstdlib> 3 #include<cstring> 4 #include<bitset> 5 using namespace std; 6 #define maxn 10010 7 #define maxm 1000010 8 9 struct node 10 { 11 int end; 12 node *ne; 13 }edge[maxm],*head[maxn]; 14 int l[maxn],n,m,tot,ast[502][502]; 15 bitset <maxn> used; 16 17 void addedge(int x,int y) 18 { 19 edge[tot].end=y; 20 edge[tot].ne=head[x]; 21 head[x]=edge+tot++; 22 } 23 bool find(int now) 24 { 25 for (node *p=head[now];p;p=p->ne) 26 { 27 int t=p->end; 28 if (!used[t]) 29 { 30 used[t]=1; 31 if (!l[t] || find(l[t])) 32 { 33 l[t]=now; 34 return 1; 35 } 36 } 37 } 38 return 0; 39 } 40 int hungary() 41 { 42 int ans=0; 43 for (int i=1;i<=n;i++) 44 { 45 used.reset(); 46 if (find(i)) ans++; 47 } 48 return ans; 49 } 50 51 int main() 52 { 53 scanf("%d%d",&n,&m); 54 for (int i=1;i<=m;i++) 55 { 56 int x,y; 57 scanf("%d%d",&x,&y); 58 addedge(x,y); 59 } 60 printf("%d\n",hungary()); 61 return 0; 62 }
你经营着一家出租车公司,现在有n个业务要求你在t时刻前往 (x1,y2) 运送顾客前往 (x2,y2) 从某地到某地的用时为其曼哈顿距离的值。问最少要派多少辆出租车才能满足所有订单。
对于订单 i,j (设j在i之后) 如果同一辆车可以完成这两个订单,那么从i到j连一条边。求一次最小路径覆盖即可。即对原图每个点拆点做匹配,最小路径覆盖=原图点数-匹配数。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstdio> 2 #include<cstdlib> 3 #include<cstring> 4 #include<bitset> 5 using namespace std; 6 #define maxn 502 7 #define maxm 125010 8 9 struct node 10 { 11 int end; 12 node *ne; 13 }edge[maxm<<1],*head[maxn<<1]; 14 int n,m,l[maxn<<1],tot,Time[maxn][2],pos[maxn][2]; 15 bitset <maxn<<1> used; 16 17 void addedge(int x,int y) 18 { 19 edge[tot].end=y; 20 edge[tot].ne=head[x]; 21 head[x]=edge+tot++; 22 } 23 24 bool find(int now) 25 { 26 for (node *p=head[now];p;p=p->ne) 27 { 28 if (!used[p->end]) 29 { 30 used[p->end]=1; 31 if (!l[p->end] || find(l[p->end])) 32 { 33 l[p->end]=now; 34 return 1; 35 } 36 } 37 } 38 return 0; 39 } 40 int hungary() 41 { 42 int ans=0; 43 for (int i=1;i<=n;i++) 44 { 45 used.reset(); 46 if (find(i)) ans++; 47 } 48 return ans; 49 } 50 51 int main() 52 { 53 int _; 54 scanf("%d",&_); 55 while (_--) 56 { 57 tot=0; 58 memset(l,0,sizeof(l)); 59 memset(head,0,sizeof(head)); 60 scanf("%d",&n); 61 for (int i=1;i<=n;i++) 62 { 63 char str[10]; 64 int a,b,x,y,hh,mm; 65 scanf("%s%d%d%d%d",str,&a,&b,&x,&y); 66 hh=(str[0]-48)*10+(str[1]-48); 67 mm=(str[3]-48)*10+(str[4]-48); 68 Time[i][0]=hh*60+mm; 69 Time[i][1]=abs(a-x)+abs(b-y); 70 pos[i][0]=a*1000+b; 71 pos[i][1]=x*1000+y; 72 } 73 for (int i=1;i<=n;i++) 74 for (int j=i+1;j<=n;j++) 75 { 76 int t=abs(pos[j][0]/1000-pos[i][1]/1000)+abs(pos[j][0]%1000-pos[i][1]%1000); 77 if ((Time[j][0]-Time[i][0])>(t+Time[i][1])) 78 addedge(i,j+n); 79 } 80 printf("%d\n",n-hungary()); 81 } 82 return 0; 83 }
给你一个有向无环图,你可以在任意点放一个机器人,机器人会沿着任一条路径走,求遍历所有点所需要的最小的机器人数量。
即允许走重复路径的最小路径覆盖。对原图做一次传递闭包(floyd),如果从i有一条路径到j,即连一条从i到j的边,然后就变成简单的最小路径覆盖了。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstdio> 2 #include<cstdlib> 3 #include<cstring> 4 #include<bitset> 5 using namespace std; 6 #define maxn 1010 7 #define maxm 260000 8 9 struct node 10 { 11 int end; 12 node *ne; 13 }edge[maxm],*head[maxn]; 14 int n,m,tot,l[maxn]; 15 bool f[maxn][maxn]; 16 bitset <maxn> u; 17 18 void addedge(int x,int y) 19 { 20 edge[tot].end=y; 21 edge[tot].ne=head[x]; 22 head[x]=edge+tot++; 23 } 24 void floyd() 25 { 26 for (int k=1;k<=n;k++) 27 for (int i=1;i<=n;i++) 28 for (int j=1;j<=n;j++) 29 if (!f[i][j]) f[i][j]=f[i][k]&f[k][j]; 30 for (int i=1;i<=n;i++) 31 for (int j=1;j<=n;j++) 32 if (i!=j && f[i][j]) addedge(i,j+n); 33 } 34 bool find(int x) 35 { 36 for (node *p=head[x];p;p=p->ne) 37 if (!u[p->end]) 38 { 39 u[p->end]=1; 40 if (!l[p->end] || find(l[p->end])) 41 { 42 l[p->end]=x; 43 return 1; 44 } 45 } 46 return 0; 47 } 48 int hungary() 49 { 50 int ans=0; 51 for (int i=1;i<=n;i++) 52 { 53 u.reset(); 54 if (find(i)) ans++; 55 } 56 return n-ans; 57 } 58 59 int main() 60 { 61 while (~scanf("%d%d",&n,&m)) 62 { 63 if (n==0 && m==0) break; 64 if (m==0) 65 { 66 printf("%d\n",n); 67 continue; 68 } 69 memset(head,0,sizeof(head)); 70 memset(f,0,sizeof(f)); 71 memset(l,0,sizeof(l)); 72 tot=0; 73 for (int i=1;i<=m;i++) 74 { 75 int x,y; 76 scanf("%d%d",&x,&y); 77 f[x][y]=1; 78 } 79 floyd(); 80 printf("%d\n",hungary()); 81 } 82 return 0; 83 }
有n个女孩,m个男孩,女孩之间相互认识,男孩之间也相互认识,有的女孩与有的男孩相互认识,现在要找出一堆人,使他们都相互认识,求这一堆人的最大人数。
即求最大团。有一个定理是最大团=补图的最大独立集。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstdio> 2 #include<cstdlib> 3 #include<cstring> 4 #include<bitset> 5 using namespace std; 6 #define maxn 205 7 8 int n,m,k,l[maxn]; 9 bool g[maxn][maxn],w[maxn][maxn]; 10 bitset<maxn> used; 11 12 bool find(int x) 13 { 14 for (int i=1;i<=m;i++) 15 if (!used[i] && w[x][i]) 16 { 17 used[i]=1; 18 if (!l[i] || find(l[i])) 19 { 20 l[i]=x; 21 return 1; 22 } 23 } 24 return 0; 25 } 26 int hungary() 27 { 28 int ans=0; 29 for (int i=1;i<=n;i++) 30 { 31 used.reset(); 32 if (find(i)) ans++; 33 } 34 return ans; 35 } 36 37 int main() 38 { 39 int _=0; 40 while (~scanf("%d%d%d",&n,&m,&k)) 41 { 42 if (n==0 && m==0 && k==0) break; 43 memset(g,0,sizeof(g)); 44 memset(w,0,sizeof(w)); 45 memset(l,0,sizeof(l)); 46 for (int i=1;i<=k;i++) 47 { 48 int x,y; 49 scanf("%d%d",&x,&y); 50 g[x][y]=1; 51 } 52 for (int i=1;i<=n;i++) 53 for (int j=1;j<=m;j++) 54 if (!g[i][j]) w[i][j]=1; 55 printf("Case %d: %d\n",++_,n+m-hungary()); 56 } 57 return 0; 58 }
然后是带权匹配的一些题:
再推荐一篇讲km的文章:http://blog.sina.com.cn/s/blog_691ce2b701016reh.html
在一个n*m的棋盘上,有k个人和k个家,要让每个人回到其中任一个家,求所需的最小步数和。
算出每个人回每个家的步数,然后km即可,注意km求的是最大权匹配,可以改造km,或者将边权加成负的,最后输出最大权的相反数。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstdio> 2 #include<cstdlib> 3 #include<cstring> 4 #include<bitset> 5 using namespace std; 6 #define maxn 250 7 #define maxm 15000 8 #define inf 0x3f3f3f3f 9 #define min(a,b) (a)<(b)?(a):(b) 10 #define max(a,b) (a)>(b)?(a):(b) 11 12 struct node 13 { 14 int end,v; 15 node *ne; 16 }edge[maxm],*head[maxn]; 17 int n,m,k,l[maxn],lx[maxn],ly[maxn],tot,slack[maxn],mid[maxn>>1],hid[maxn>>1]; 18 bitset <maxn> nx,ny; 19 20 void addedge(int x,int y,int z) 21 { 22 edge[tot].end=y; 23 edge[tot].v=z; 24 edge[tot].ne=head[x]; 25 head[x]=edge+tot++; 26 } 27 bool find(int x) 28 { 29 nx[x]=1; 30 for (node *p=head[x];p;p=p->ne) 31 if (!ny[p->end]) 32 { 33 int t=lx[x]+ly[p->end]-p->v; 34 if (t==0) 35 { 36 ny[p->end]=1; 37 if (!l[p->end] || find(l[p->end])) 38 { 39 l[p->end]=x; 40 return 1; 41 } 42 } 43 else if (slack[p->end]>t) slack[p->end]=t; 44 } 45 return 0; 46 } 47 int km() 48 { 49 memset(lx,-0x3f,sizeof(lx)); 50 memset(ly,0,sizeof(ly)); 51 memset(l,0,sizeof(l)); 52 for (int i=1;i<=k;i++) 53 for (node *p=head[i];p;p=p->ne) 54 if (lx[i]<p->v) lx[i]=p->v; 55 for (int i=1;i<=k;i++) 56 { 57 memset(slack,0x3f,sizeof(slack)); 58 while (1) 59 { 60 nx.reset(); 61 ny.reset(); 62 if (find(i)) break; 63 int t=inf; 64 for (int j=k+1;j<=k*2;j++) 65 if (!ny[j] && t>slack[j]) t=slack[j]; 66 for (int j=1;j<=k;j++) 67 if (nx[j]) lx[j]-=t; 68 for (int j=k+1;j<=k*2;j++) 69 { 70 if (ny[j]) ly[j]+=t; 71 else slack[j]-=t; 72 } 73 } 74 } 75 int ans=0; 76 for (int i=k+1;i<=k*2;i++) 77 if (l[i]) ans+=(lx[l[i]]+ly[i]); 78 return ans; 79 } 80 81 int main() 82 { 83 while (~scanf("%d%d\n",&n,&m)) 84 { 85 if (n==0 && m==0) break; 86 tot=0; 87 memset(head,0,sizeof(head)); 88 char c; 89 int x=0,y=0; 90 for (int i=1;i<=n;i++) 91 { 92 for (int j=1;j<=m;j++) 93 { 94 scanf("%c",&c); 95 if (c=='H') hid[++y]=i*1000+j; 96 if (c=='m') mid[++x]=i*1000+j; 97 } 98 scanf("\n"); 99 } 100 k=x; 101 for (int i=1;i<=k;i++) 102 for (int j=1;j<=k;j++) 103 addedge(i,j+k,-(abs(mid[i]/1000-hid[j]/1000)+abs(mid[i]%1000-hid[j]%1000))); 104 printf("%d\n",-km()); 105 } 106 }
你有n个人,要迎战敌方的m个人,与敌人作战会让你的人减血,给出k组作战情况,求你这边最少的减血量。
与上一题一样,只是输入略蛋疼,它是给的人名……我就拿它来练hash了。。。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstdio> 2 #include<cstdlib> 3 #include<cstring> 4 #include<bitset> 5 using namespace std; 6 #define maxn 510 7 #define maxm 40010 8 #define key 12345 9 #define hash_size (1<<21) 10 #define inf 0x3f3f3f3f 11 #define ull unsigned long long 12 13 struct node 14 { 15 int v,end; 16 ull hash_v; 17 node *ne; 18 }edge[maxm],*head[maxn],hash_e[maxn<<1],*hash_tag[hash_size+1]; 19 int n,m,k,tot,l[maxn],slack[maxn],cnt,lx[maxn],ly[maxn],id[maxn]; 20 bitset <maxn> ux,uy; 21 22 void addedge(int x,int y,int z) 23 { 24 edge[tot].v=z; 25 edge[tot].end=y; 26 edge[tot].ne=head[x]; 27 head[x]=edge+tot++; 28 } 29 int hash_add(char *str) 30 { 31 ull x=0; 32 for (int len=0;str[len];len++) 33 x=x*key+(int)str[len]; 34 int t=x&(hash_size); 35 for (node *p=hash_tag[t];p;p=p->ne) 36 if (p->hash_v==x) return p-hash_e; 37 hash_e[++cnt].hash_v=x; 38 hash_e[cnt].ne=hash_tag[t]; 39 hash_tag[t]=hash_e+cnt; 40 return cnt; 41 } 42 bool find(int x) 43 { 44 ux[x]=1; 45 for (node *p=head[x];p;p=p->ne) 46 if (!uy[p->end]) 47 { 48 int t=lx[x]+ly[p->end]-p->v; 49 if (t==0) 50 { 51 uy[p->end]=1; 52 if (!l[p->end] || find(l[p->end])) 53 { 54 l[p->end]=x; 55 return 1; 56 } 57 } 58 else if (slack[p->end]>t) slack[p->end]=t; 59 } 60 return 0; 61 } 62 int km() 63 { 64 memset(lx,-0x3f,sizeof(lx)); 65 memset(ly,0,sizeof(ly)); 66 memset(l,0,sizeof(l)); 67 for (int i=1;i<=n;i++) 68 for (node *p=head[i];p;p=p->ne) 69 if (p->v>lx[i]) lx[i]=p->v; 70 for (int i=1;i<=n;i++) 71 { 72 memset(slack,0x3f,sizeof(slack)); 73 while (1) 74 { 75 ux.reset(); 76 uy.reset(); 77 if (find(i)) break; 78 int d=inf; 79 for (int j=n+1;j<=n+m;j++) 80 if (!uy[j] && d>slack[j]) 81 d=slack[j]; 82 for (int j=1;j<=n;j++) if (ux[j]) lx[j]-=d; 83 for (int j=n+1;j<=n+m;j++) 84 { 85 if (uy[j]) ly[j]+=d; 86 else slack[j]-=d; 87 } 88 } 89 } 90 int ans=0; 91 for (int i=n+1;i<=n+m;i++) 92 if (l[i]) ans+=(lx[l[i]]+ly[i]); 93 return ans; 94 } 95 96 int main() 97 { 98 while(~scanf("%d%d%d",&n,&m,&k)) 99 { 100 tot=cnt=0; 101 int xcnt=0,ycnt=n,xmax=0,ymax=0; 102 memset(hash_tag,0,sizeof(hash_tag)); 103 memset(head,0,sizeof(head)); 104 for (int i=1;i<=k;i++) 105 { 106 char c1[30],c2[30]; 107 int x,t1,t2; 108 scanf("%s%s%d",c1,c2,&x); 109 t1=hash_add(c1); 110 t2=hash_add(c2); 111 if (t1>xmax) xmax=t1,id[t1]=++xcnt; 112 if (t2>ymax) ymax=t2,id[t2]=++ycnt; 113 addedge(id[t1],id[t2],-x); 114 } 115 printf("%d\n",-km()); 116 } 117 }
给出一个n个点,m条边的无向图,每条边有边权。现在要你修改一些边的边权,使得前n-1条边是最小生成树,要求总的修改量最小,输出修改后每条边的边权。
题中有一个隐含的不等式,可以与km联系起来。详细题解与代码戳这里:http://www.cnblogs.com/wangziyun/archive/2013/03/31/2991389.html