题意
给出一个R*C大小的网格,网格上面放了一些目标。可以在网格外发射子弹,子弹会沿着垂直或者水平方向飞行,并且打掉飞行路径上的所有目标。你的任务是计算最少需要多少子弹,各从哪些位置发射,才能把所有目标全部打掉。
分析
啊!原来这个模型叫 最小覆盖模型啊!难道不是最小割直接做嘛??
二分图最小覆盖:既选择尽量少的点,使得每条边至少有一个端点被选中。可以证明,最小覆盖数等于最大匹配数。
本题的建模方法:
将每一行看作一个X结点,每一列看作一个Y结点,每个目标对应一条边。这样,子弹打掉左右的目标意味着每条边至少有一个节点被选中。
好吧,我还是说一下我最小割的理解吧。建模一开始还是一样的,每一行为X结点,每一列为Y结点,每个目标对应一条从X到Y的一条边。然后从s向每个X结点连一条容量为1的边。然后每个Y结点都向t结点连一条容量为1的边。求最小割的时候一定是个割从s连出的边或者连向t的边。而如果割s->u这条边,那么就代表这一行打子弹,割v->t也同理。
如果只是输出这个最小的子弹数,那么到这里就结束了,是个非常简单的最小割。但是这个题要输出方案!
按照最小割来说的话,我们要知道,我们割的是哪一条边。一开始我想当然以为就是输出满流的边,但是很显然不对啊!
然后,然后,然后我就去可耻的百度了。
哪些边是最小割的边呢?我们知道,跑完最小割是以后是把所有的点都分为X阵营和Y阵营。那么连接着两个阵营的边就是最小割的边。那么我们只要找出哪些点是X哪些点是Y就可以了。怎么找呢?我们跑完dinic以后,是得到了一个不存在增广路的残量网络的。此时X和Y是分开的,那么只要从s结点开始顺着残量网络的边(包括反向边)跑一个DFS,只要能跑到的点都是X阵营。
具体看下面的代码吧~
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #include <iostream> 5 #include <queue> 6 7 using namespace std; 8 const int maxn=1000000+10; 9 const int INF=2147480000; 10 11 struct Dinic{ 12 int head[maxn],Next[3*maxn],to[3*maxn],cap[3*maxn],flow[3*maxn]; 13 int sz,n,m,s,t; 14 bool vis[maxn]; 15 int cur[maxn],d[maxn]; 16 void init(int n){ 17 this->n=n; 18 memset(head,-1,sizeof(head)); 19 this->sz=-1; 20 } 21 void add_edge(int a,int b,int c){ 22 ++sz; 23 to[sz]=b; 24 cap[sz]=c;flow[sz]=0; 25 Next[sz]=head[a];head[a]=sz; 26 ++sz; 27 to[sz]=a; 28 cap[sz]=c;flow[sz]=c; 29 Next[sz]=head[b];head[b]=sz; 30 } 31 bool BFS(){ 32 memset(vis,0,sizeof(vis)); 33 queue<int>Q; 34 vis[s]=1; 35 d[s]=0; 36 Q.push(s); 37 while(!Q.empty()){ 38 int u=Q.front();Q.pop(); 39 for(int i=head[u];i!=-1;i=Next[i]){ 40 int v=to[i]; 41 if(!vis[v]&&cap[i]>flow[i]){ 42 vis[v]=1; 43 d[v]=d[u]+1; 44 Q.push(v); 45 } 46 } 47 } 48 return vis[t]; 49 } 50 int DFS(int x,int a){ 51 if(x==t||a==0)return a; 52 int Flow=0,f; 53 for(int& i=cur[x];i!=-1;i=Next[i]){ 54 int v=to[i]; 55 if(d[v]==d[x]+1&&(f=DFS(v,min(a,cap[i]-flow[i])))>0){ 56 Flow+=f; 57 flow[i]+=f; 58 flow[i^1]-=f; 59 a-=f; 60 if(a==0)break; 61 } 62 } 63 return Flow; 64 } 65 int Maxflow(int s,int t){ 66 this->s=s,this->t=t; 67 int Flow=0; 68 while(BFS()){ 69 for(int i=0;i<=n;i++) 70 cur[i]=head[i]; 71 72 Flow+=DFS(s,INF); 73 } 74 return Flow; 75 } 76 }dinic; 77 int R,C,N; 78 int foot[maxn]; 79 void get_ans(int u){ 80 foot[u]=1; 81 for(int i=dinic.head[u];i!=-1;i=dinic.Next[i]){ 82 int v=dinic.to[i]; 83 if(!foot[v]&&dinic.cap[i]>dinic.flow[i]) 84 get_ans(v); 85 } 86 return; 87 } 88 89 int main(){ 90 while(scanf("%d%d%d",&R,&C,&N)!=EOF&&(R||C||N)){ 91 memset(foot,0,sizeof(foot)); 92 dinic.init(R+C+5); 93 int r,c; 94 for(int i=1;i<=N;i++){ 95 scanf("%d%d",&r,&c); 96 dinic.add_edge(r,c+R,1); 97 } 98 for(int i=1;i<=R;i++) 99 dinic.add_edge(0,i,1); 100 for(int i=1;i<=C;i++) 101 dinic.add_edge(i+R,C+R+1,1); 102 int ans=dinic.Maxflow(0,C+R+1); 103 printf("%d ",ans); 104 get_ans(0); 105 for(int i=1;i<=R;i++){ 106 if(!foot[i]){ 107 printf("r%d ",i); 108 } 109 } 110 for(int i=1;i<=C;i++){ 111 int u=i+R; 112 if(foot[u]){ 113 printf("c%d ",i); 114 } 115 } 116 printf(" "); 117 } 118 return 0; 119 }