这里以TJOI2016的游戏为例
Description
在2016年,佳缘姐姐喜欢上了一款游戏,叫做泡泡堂。简单的说,这个游戏就是在一张地图上放上若干个炸弹,看是否能炸到对手,或者躲开对手的炸弹。在玩游戏的过程中,小H想到了这样一个问题:当给定一张地图,在这张地图上最多能放上多少个炸弹能使得任意两个炸弹之间不会互相炸到。炸弹能炸到的范围是该炸弹所在的一行和一列,炸弹的威力可以穿透软石头,但是不能穿透硬石头。
给定一张n ∗ m的网格地图:
其中∗代表空地,炸弹的威力可以穿透,可以在空地上放置一枚炸弹。
x代表软石头,炸弹的威力可以穿透,不能在此放置炸弹。
#代表硬石头,炸弹的威力是不能穿透的,不能在此放置炸弹。
例如:给出1∗4的网格地图∗xx∗,这个地图上最多只能放置一个炸弹。给出另一个1 ∗ 4的网格地图∗x#∗,这个地图最多能放置两个炸弹。
现在小H任意给出一张n ∗ m的网格地图,问你最多能放置多少炸弹。
Input
第一行输入两个正整数n,m,n表示地图的行数,m 表示地图的列数。1 ≤
n,m ≤ 50。
接下来输入n行m列个字符,代表网格地图。
Output
输出一个整数a,表示最多能放置炸弹的个数。
Sample Input
4 4
#***
*#**
**#*
xxx#
Sample Output
5
对于本题的分析可以看一下这里
对于二分图最大匹配,有两种较实用的方法:一种是转化成最大流问题求解,一种是匈牙利算法。
先讲第一种吧。如何将二分图转化为最大流问题?其实很容易,不妨对二分图作如下变形。
将原图中的所有无向边 e 改成有向边,方向从 U 到 V ,容量为1。增加源点s和汇点t,从s向所有顶点 u∈U 连一条容量为1的边,从所有的顶点 v∈V 向t连一条容量为1的边。
新得到的图G’中s-t的最大流就是原二分图G中最大匹配的匹配数,而 U - V 之间流量为正的边集合就是最大匹配。下面用的是复杂度O(|V||E|)的Ford-Fulkerson。
#include
#include
#include
#include
#include
#define oo 2000000000
#define M 55
using namespace std;
template <class T>
inline void Rd(T &res){
char c;res=0;int k=1;
while(c=getchar(),c<48&&c!='-');
if(c=='-'){k=-1;c='0';}
do{
res=(res<<3)+(res<<1)+(c^48);
}while(c=getchar(),c>=48);
res*=k;
}
int used[M*M];
struct edge{
int to,cap,rev;
};
vector G[M*M];
void add_edge(int u,int v,int cap){
G[u].push_back((edge){v,cap,G[v].size()});
G[v].push_back((edge){u,0,G[u].size()-1});
}
int dfs(int s,int t,int f){
if(s==t)return f;
used[s]=1;
for(int i=0;iif(!used[e.to]&&e.cap>0){
int d=dfs(e.to,t,min(f,e.cap));
if(d>0){
e.cap-=d;
G[e.to][e.rev].cap+=d;
return d;
}
}
}
}
int max_flow(int s,int t){
int flow=0;
while(1){
memset(used,0,sizeof(used));
int f=dfs(s,t,oo);
if(f==0)return flow;
flow+=f;
}
}
int n,m;
int row[M][M],col[M][M];
int rowid[M*M],colid[M*M];
int R,C;
char map[M][M];
int main(){
Rd(n);Rd(m);
for(int i=1;i<=n;i++)
scanf("%s",map[i]+1);
R=1,C=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
if(map[i][j]!='#'&&!row[i][j]){
for(int k=j;k<=m&&map[i][k]!='#';k++)row[i][k]=R;
R++;
}
if(map[i][j]!='#'&&!col[i][j]){
for(int k=i;k<=n&&map[k][j]!='#';k++)col[k][j]=C;
C++;
}
}
for(int i=1;i//将横块编号为1~R-1
for(int i=1;i1;//将竖块编号为R~R+C-2
int s=0;//将s编号为0
int t=R+C-1;//将t编号为R+C-1
for(int i=1;i1);
for(int i=1;i1);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(map[i][j]=='*'&&row[i][j]&&col[i][j])add_edge(rowid[row[i][j]],colid[col[i][j]],1);
printf("%d\n",max_flow(s,t));
return 0;
}
匈牙利算法,用来二分图匹配很简单。
#include
#include
#include
#include
#define M 55
using namespace std;
template <class T>
inline void Rd(T &res){
char c;res=0;int k=1;
while(c=getchar(),c<48&&c!='-');
if(c=='-'){k=-1;c='0';}
do{
res=(res<<3)+(res<<1)+(c^48);
}while(c=getchar(),c>=48);
res*=k;
}
int used[M*M];
int n,m;
int row[M][M],col[M][M];
int rowid[M*M],colid[M*M];
int g[M*M][M*M],match[M*M];
int R,C,ans;
bool dfs(int x){
for(int i=1;iif(g[x][colid[i]]&&!used[colid[i]]){
used[colid[i]]=1;
if(match[colid[i]]==-1||dfs(match[colid[i]])){
match[colid[i]]=x;
return true;
}
}
return false;
}
char map[M][M];
int main(){
Rd(n);Rd(m);
for(int i=1;i<=n;i++)
scanf("%s",map[i]+1);
R=1,C=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
if(map[i][j]!='#'&&!row[i][j]){
for(int k=j;k<=m&&map[i][k]!='#';k++)row[i][k]=R;
R++;
}
if(map[i][j]!='#'&&!col[i][j]){
for(int k=i;k<=n&&map[k][j]!='#';k++)col[k][j]=C;
C++;
}
}
for(int i=1;i//将横块编号为1~R-1
for(int i=1;i1;//将竖块编号为R~R+C-21
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(map[i][j]=='*')
g[rowid[row[i][j]]][colid[col[i][j]]]=1;
memset(match,-1,sizeof(match));
for(int i=1;imemset(used,0,sizeof(used));
if(dfs(rowid[i]))ans++;
}
printf("%d\n",ans);
return 0;
}