题目网址点击打开链接
关于这个题,首先思路是将每个行和列分开来看,即将列和行分为两个部分,两个部分之间的边就是题目中的炸弹,连接其所在的行和列。寻找最少的点可以覆盖所有的边,即是最小点覆盖集,
在做这个题以前,首先要知道一下几点:
1增广路:
(引用)
二分图:简单来说,如果图中点可以被分为两组,并且使得所有边都跨越组的边界,则这就是一个二分图。准确地说:把一个图的顶点划分为两个不相交集 U 和 V ,使得每一条边都分别连接 U 、 V
中的顶点。如果存在这样的划分,则此图为一个二分图。二分图的一个等价定义是:不含有「含奇数条边的环」的图。图 1 是一个二分图。为了清晰,我们以后都把它画成图 2 的形式。
匹配:在图论中,一个「匹配」(matching)是一个边的集合,其中任意两条边都没有公共顶点。例如,图 3、图 4 中红色的边就是图 2 的匹配。
我们定义匹配点、匹配边、未匹配点、非匹配边,它们的含义非常显然。例如图 3 中 1、4、5、7 为匹配点,其他顶点为未匹配点;1-5、4-7为匹配边,其他边为非匹配边。
最大匹配:一个图所有匹配中,所含匹配边数最多的匹配,称为这个图的最大匹配。图 4 是一个最大匹配,它包含 4 条匹配边。
增广路:从一个未匹配点出发,走交替路,如果途径另一个未匹配点(出发的点不算),则这条交替路称为增广路(agumenting path)。例如,图 5 中的一条增广路如图 6 所示(图中的匹配点均用红色标出):
增广路有一个重要特点:非匹配边比匹配边多一条。因此,研究增广路的意义是改进匹配。只要把增广路中的匹配边和非匹配边的身份交换即可。由于中间的匹配节点不存在其他相连的匹配边,所以这样做不会破坏匹配的性质。交换后,图中的匹配边数目比原来多了 1 条。
我们可以通过不停地找增广路来增加匹配中的匹配边和匹配点。找不到增广路时,达到最大匹配(这是增广路定理)。匈牙利算法正是这么做的。
2 König定理:最小点覆盖数 = 最大匹配数。
3最后对于题目中rc的取法,
妈耶,一个一个的解释都深深叨叨的原谅我见识浅薄,
对于一个二分图,求出其最大匹配。然后取左侧(S侧)所有的未匹配点,按照增广路算法,寻找交错路径(路径上的点都是匹配点),标记掉路径上的所有点(不存在重复标记)。那么左侧(S侧)所有的未标记点,与右侧(T侧)所有的标记点,就是实现最小点覆盖的点;
以上是他们的解释,额突然发现我也描述不了,看代码其实比看说的还好懂一些,
对于总数,根据定理,直接跑一个匈牙利就可以求出答案
对于rc的答案,其实是这样的,可以这样想,你现在求r,c输出 以前已经把xy所有的可以相连的点已经连好了,但是此时还有一部分的x没有与y相连(也就是有些石头还没法被哄掉),把这一部分x拿出来,去与y相连,这样连好的y(也就是c列,就是答案的一部分),这样就可以把剩下的用列去打掉,而答案的另一部分就是原来的x(把第二步里面处理的x抠掉以后即是答案)
至于正确性,去看其他人的博客的证明:
就这样吧撤退,去操场,程序员爱惜身体
#include <iostream>
#include <cstring>
#include <string>
#include <cstdio>
#include<algorithm>
#include<vector>
#include<set>
#include<map>
using namespace std;
const int maxn=1000+5;
int s[maxn];
int t[maxn];
int lef[maxn];
int righ[maxn];
int eg[maxn][maxn];
int r,c,n;
void init()
{
memset(eg,0,sizeof(eg));
memset(lef,0,sizeof(lef));
memset(righ,0,sizeof(righ));
}
int match(int x)
{
s[x]=1;
for(int i=1;i<=c;i++)
{
if(eg[x][i]&&!t[i])
{
t[i]=1;
if(!lef[i]||match(lef[i]))
{
lef[i]=x;
return 1;
}
}
}
return 0;
}
void clearst()
{
memset(s,0,sizeof(s));
memset(t,0,sizeof(t));
}
int getans()
{
int ans=0;
clearst();
for(int i=1;i<=r;i++)
{
memset(t,0,sizeof(t));
if(match(i))
ans++;
}
printf("%d",ans);
}
void print()
{
clearst();
for(int i=1;i<=c;i++)
{
if(lef[i])
{
righ[lef[i]]=1;//去除标记的点
}
}
for(int i=1;i<=r;i++)
{
if(!righ[i]) //跑
{
match(i);
}
}
for(int i=1;i<=r;i++)
if(!s[i])
printf(" r%d",i);
for(int i=1;i<=c;i++)
if(t[i])
printf(" c%d",i);
printf("\n");
}
int main()
{
while(cin>>r>>c>>n&&r&&c&&n)
{
init();
for(int i=0;i<n;i++)
{
int a,b;
cin>>a>>b;
eg[a][b]=1;
}
//第一步简单匈牙利
getans();
//第二步处理
print();
}
return 0;
}
关于博客的引用:1
点击打开链接
2;
;;
;
;;;