题意
给定一个 n ∗ m n*m n∗m 的网格,每个格子内可能是 o o o 或 ∗ * ∗,现在需要用 1 ∗ 2 1*2 1∗2 的方块将网格上所有的 ∗ * ∗ 遮掉,且方块可以重叠。问最少需要多少方块。
二分图思想:将所有 ∗ * ∗ 格与它相邻的 ∗ * ∗ 格子进行连边,由于每一条边即为可能需放置的方块,容易贪心想到尽量让一个方块正好覆盖两个 ∗ * ∗,因此可以通过先求最大匹配数 m a x max max ,则此时被覆盖的点数为 2 ∗ m a x 2*max 2∗max,而剩下的所有孤寡点都需要一个独立方块进行覆盖,则最后所需要的方块数为 m a x + ( n − 2 ∗ m a x ) max+(n-2*max) max+(n−2∗max)
也可以采用: 最 小 路 径 覆 盖 = 顶 点 数 – 最 大 二 分 匹 配 数 / 2 最小路径覆盖 = 顶点数 – 最大二分匹配数/2 最小路径覆盖=顶点数–最大二分匹配数/2
#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
const int N=500,M=500;
int dx[4]={0,1,0,-1};
int dy[4]={1,0,-1,0};
int w[N][N];//是否连边
int s[N][N],link[M];
bool vis[M];
int n,m,sum;
int dfs(int x){
for(int i=1;i<=sum;i++){
if(vis[i]||w[x][i]==0)
continue;
vis[i]=1;
if(link[i]==0||dfs(link[i])){
link[i]=x;
return 1;
}
}
return 0;
}
void solve(){
cin>>n>>m;
memset(w,0,sizeof(w));
memset(s,0,sizeof(s));
memset(link,0,sizeof(link));
memset(vis,0,sizeof(vis));
sum=0;
getchar();
char op;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>op;
if(op=='*'){
sum++;
s[i][j]=sum;
}
}
getchar();
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(s[i][j]){
for(int k=0;k<4;k++){
if(s[i+dx[k]][j+dy[k]])
w[s[i][j]][s[i+dx[k]][j+dy[k]]]=1;//相邻两格为*连边
}
}
}
}
int num=0;
for(int i=1;i<=sum;i++){
memset(vis,0,sizeof(vis));
if(dfs(i))
num++;
}
//cout<<sum<<endl;
num/=2;
cout<<num+(sum-2*num)<<endl;
}
signed main(){
int t;
cin>>t;
while(t--)
solve();
return 0;
}