HDU 4185 Oil Skimming(二分图最大匹配)
http://acm.hdu.edu.cn/showproblem.php?pid=4185
题意:
有个N*N的字符矩阵,你必须用竖直或水平的1*2小矩阵去覆盖字符矩阵中相邻的两个”#”字符. 且你用的1*2小矩阵不能重叠且只能覆盖”#”字符. 问你最多能用多少个1*2的小矩阵?
分析:
把原字符矩阵的所有”#”都编号看成一个一个的节点.如果有两个”#”相邻,那么就在它们之间连接一条边. 那么我们就得到了一个二分图(该图必定二分,因为矩阵就是二分图 且 行号+列号==奇数的点与行号+列数号==偶数的点 分别属于不同的两个点集).
现在的问题是我们要求这个二分图的最大匹配边数,找出尽量多的匹配边. 那么这些匹配边就是一个个不重叠覆盖了”#”字符的1*2矩阵.
将原图的所有”#”字符所在的格子分成左右两个点集算二分图可以,不过需要(用行号加列号的奇偶性)将格子分为左右两边,有点麻烦. 这里我们直接将原图翻倍,求出翻倍图的匹配数/2 即是答案.(类似于POJ1466http://blog.youkuaiyun.com/u013480600/article/details/38638219).
AC代码:
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int maxn=600+10;
struct Max_Match
{
int n;
vector<int> g[maxn];
bool vis[maxn];
int left[maxn];
void init(int n)
{
this->n=n;
for(int i=1;i<=n;i++) g[i].clear();
memset(left,-1,sizeof(left));
}
bool match(int u)
{
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i];
if(!vis[v])
{
vis[v]=true;
if(left[v]==-1 || match(left[v]))
{
left[v]=u;
return true;
}
}
}
return false;
}
int solve()
{
int ans=0;
for(int i=1;i<=n;i++)
{
memset(vis,0,sizeof(vis));
if(match(i)) ans++;
}
return ans;
}
}MM;
int map[maxn][maxn];
int main()
{
int T; scanf("%d",&T);
for(int kase=1; kase<=T; ++kase)
{
int N;
int n=0;//记录'#'字符的个数
scanf("%d",&N);
memset(map,0,sizeof(map));
for(int i=1;i<=N;i++)
for(int j=1;j<=N;j++)
{
char ch;
scanf(" %c",&ch);
if(ch=='#') map[i][j]=++n; //'#'字符编号
else map[i][j]=0;
}
MM.init(n);
for(int i=1;i<=N;i++)
for(int j=1;j<=N;j++)if(map[i][j]>0)
{ //这里不需要添加反向边,因为后面还会遇到此边一次
if(map[i+1][j]>0) MM.g[map[i][j]].push_back(map[i+1][j]);
if(map[i-1][j]>0) MM.g[map[i][j]].push_back(map[i-1][j]);
if(map[i][j+1]>0) MM.g[map[i][j]].push_back(map[i][j+1]);
if(map[i][j-1]>0) MM.g[map[i][j]].push_back(map[i][j-1]);
}
printf("Case %d: %d\n",kase,MM.solve()/2);
}
return 0;
}