二分图相关 小结

二分图匹配这一块的一大堆概念各种绕,我被虐爽了。。。

特别是其中各种各样的最大——最小关系,互补关系之间的转化等等。。。各种虐心。。。

先推荐一篇很长的文章,讲得比较详细,把几乎所有的问题都涉及到了。

http://dsqiu.iteye.com/blog/1689505

 

首先是跟最大匹配有关的问题一大堆:

POJ 3041

给一个n*n的矩阵,上面有k颗小行星,你有一把枪,每次可以打一行或者一列,求最少打几次可以把小行星都蒸发掉(什么乱七八糟的。。。)

把行和列看成点,小行星看成连接所在行与列的边,就成了最小点覆盖,等于最大匹配数。

View Code
 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 }

 

POJ 2060

你经营着一家出租车公司,现在有n个业务要求你在t时刻前往 (x1,y2) 运送顾客前往 (x2,y2) 从某地到某地的用时为其曼哈顿距离的值。问最少要派多少辆出租车才能满足所有订单。

对于订单 i,j (设j在i之后) 如果同一辆车可以完成这两个订单,那么从i到j连一条边。求一次最小路径覆盖即可。即对原图每个点拆点做匹配,最小路径覆盖=原图点数-匹配数。

View Code
 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 }

 

POJ 2594

给你一个有向无环图,你可以在任意点放一个机器人,机器人会沿着任一条路径走,求遍历所有点所需要的最小的机器人数量。

即允许走重复路径的最小路径覆盖。对原图做一次传递闭包(floyd),如果从i有一条路径到j,即连一条从i到j的边,然后就变成简单的最小路径覆盖了。

View Code
 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 }

 

POJ 3692

有n个女孩,m个男孩,女孩之间相互认识,男孩之间也相互认识,有的女孩与有的男孩相互认识,现在要找出一堆人,使他们都相互认识,求这一堆人的最大人数。

即求最大团。有一个定理是最大团=补图的最大独立集。

View Code
 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

 

POJ 2195

在一个n*m的棋盘上,有k个人和k个家,要让每个人回到其中任一个家,求所需的最小步数和。

算出每个人回每个家的步数,然后km即可,注意km求的是最大权匹配,可以改造km,或者将边权加成负的,最后输出最大权的相反数。

View Code
  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 }

 

HDU 2813

你有n个人,要迎战敌方的m个人,与敌人作战会让你的人减血,给出k组作战情况,求你这边最少的减血量。

与上一题一样,只是输入略蛋疼,它是给的人名……我就拿它来练hash了。。。

View Code
  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 }

 

SGU 206

给出一个n个点,m条边的无向图,每条边有边权。现在要你修改一些边的边权,使得前n-1条边是最小生成树,要求总的修改量最小,输出修改后每条边的边权。

题中有一个隐含的不等式,可以与km联系起来。详细题解与代码戳这里:http://www.cnblogs.com/wangziyun/archive/2013/03/31/2991389.html

 

posted @ 2013-03-31 20:06  wangziyun  阅读(189)  评论(0编辑  收藏  举报
神奇的东西