题意:在方格图上打小怪,每次可以清除一整行或一整列的小怪,问最少的步数是多少,又应该在哪些位置操作(对输出顺序没有要求)。
分析:最小覆盖问题
这是一种在方格图上建立的模型:令S集表示“行”,T集表示“列”,那么小怪站的位置w(i,j),就是二分图上的边。如此建图,那么每次清除,就是把与某个点相连的边全部清除,问最少选择多少个点。(这也是最小点覆盖的概念:选择尽量少的点,使得每条边至少有一个端点被选中)
这里有一个König定理:最大二分匹配数==最小覆盖点数。
既然是求最小点覆盖,那么自然是选那些所连边数多的点,不过貌似不好安排啊?
先从简单问题开始讨论:找到必然要选的点。对于一棵树,为了覆盖到叶子节点所在的边,必须选择该条边上的两个端点之一。不用问,按照最小覆盖的原则,必然是要选非叶子节点了。把所有与选择的点相连的边都处理掉,就相当于是这棵树的最下面一层被砍掉了(叶子的爷爷成为了新的叶子)。那么对于剩下的树,仍然可以这样操作,最终得到了最小点覆盖。幸运的是,树属于二分图,而二分图虽然不一定是树(存在偶环),但可以构建出匈牙利树(交错树),是可以借鉴的。
聪明的地球人提出了如下算法:
对于一个二分图,求出其最大匹配。然后取左侧(S侧)所有的未匹配点,按照增广路算法,寻找交错路径(路径上的点都是匹配点),标记掉路径上的所有点(不存在重复标记)。那么左侧(S侧)所有的未标记点,与右侧(T侧)所有的标记点,就是实现最小点覆盖的点。
1、为什么这些点可以覆盖所有边?
如果还有没有覆盖的边,那么一定是一些左端点已标记,右端点未标记的边。而通过我们算法中的构造,不存在这样的边。为什么呢?回忆一下增广路算法,我们的起点选择的是S侧的未匹配点,之后选择一条边走到T侧(该点必然是匹配点);因为要走交错路径,就沿着匹配边回到了S侧...可以想象成每次只是从S侧走到T侧,然后沿匹配边回到S侧。所以不存在S端已标记,T端未标记的线段。所以所有边都被覆盖了。
2、为什么这些点的在数量上等于最大二分匹配的边数?
因为每条匹配边上恰好选择一个点。为什么是这样呢?注意,最小点覆盖的点集包括:S侧的未标记点,和T侧的标记点。有增广路算法可知(同上一个问题相似),若T侧的点被标记,那么同一条匹配边上的S侧的点必然也被标记——点集中不包含同一条匹配边上的两个点。S侧的未标记点,实际上是除去未匹配点,以及跟随T侧点而被标记的点后,剩余的(即选择了那些T侧未被标记的匹配边)。所以两侧的点把所有的匹配边都包含了。所以最大二分匹配数==最小覆盖点数。
3、为什么这些点就是最小值?
这个问题最简单:及使图上只有n条匹配边,我们都要用n个点才能覆盖。若再少,至少漏掉一条匹配边。所以是最小值。
(注:难得自己动手改了张图...圆圈是覆盖点,方格是标记点,箭头是交错路径,蓝线是匹配边。)
可以看看这个链接,我认为上面的解释更通俗一些。
http://www.matrix67.com/blog/?s=%E6%9C%80%E5%B0%8F%E8%A6%86%E7%9B%96%E7%82%B9
1 #include<cstdio> 2 #include<cstring> 3 #include<vector> 4 #include<algorithm> 5 #define clr(a,m) memset(a,m,sizeof(a)) 6 #define rep(i,a,b) for(int i=a;i<=b;i++) 7 using namespace std; 8 9 const int MAXN=1111; 10 11 int n,m,cnt,ans; 12 13 vector<int>G[MAXN]; 14 vector<int>row,col; 15 16 int left[MAXN],right[MAXN],S[MAXN],T[MAXN]; 17 18 void init() 19 { 20 rep(i,1,n) 21 G[i].clear(); 22 } 23 24 void read() 25 { 26 int x,y; 27 init(); 28 rep(i,1,cnt){ 29 scanf("%d%d",&x,&y); 30 G[x].push_back(y); 31 } 32 } 33 34 bool match(int u) 35 { 36 S[u]=true; 37 int sz=G[u].size(); 38 rep(i,0,sz-1){ 39 int v=G[u][i]; 40 if(!T[v]){ 41 T[v]=true; 42 if(!left[v]||match(left[v])){ 43 left[v]=u; 44 right[u]=v; 45 return true; 46 } 47 } 48 } 49 return false; 50 } 51 52 void AP() 53 { 54 rep(i,1,m)left[i]=0;//n和m写反了 55 rep(i,1,n)right[i]=0; 56 57 ans=0; 58 rep(i,1,n){ 59 rep(j,1,n)S[j]=0; 60 rep(j,1,m)T[j]=0; 61 if(match(i)) 62 ans++; 63 } 64 } 65 66 void mincover() 67 { 68 rep(i,1,n)S[i]=0;//重新标记增广路 69 rep(i,1,m)T[i]=0; 70 71 rep(i,1,n)//选择S侧的未标记点 72 if(!right[i]) 73 match(i); 74 75 row.clear(); 76 col.clear(); 77 rep(i,1,n) 78 if(!S[i]) 79 row.push_back(i); 80 rep(i,1,m) 81 if(T[i]) 82 col.push_back(i); 83 } 84 85 void print() 86 { 87 printf("%d",ans); 88 int sz=col.size(); 89 rep(i,0,sz-1) 90 printf(" c%d",col[i]); 91 sz=row.size(); 92 rep(i,0,sz-1) 93 printf(" r%d",row[i]); 94 95 96 97 printf(" "); 98 } 99 100 int main() 101 { 102 while(~scanf("%d%d%d",&n,&m,&cnt)) 103 { 104 if(!n&&!m&&!cnt) 105 return 0; 106 read(); 107 AP(); 108 mincover(); 109 print(); 110 } 111 return 0; 112 }