bzoj4554: [Tjoi2016&Heoi2016]游戏 二分图匹配
4554: [Tjoi2016&Heoi2016]游戏
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列个字符,代表网
格地图。*的个数不超过n*m个
Output
输出一个整数a,表示最多能放置炸弹的个数
Sample Input
4 4
#∗∗∗
∗#∗∗
∗∗#∗
xxx#
#∗∗∗
∗#∗∗
∗∗#∗
xxx#
Sample Output
5
最近在学二分图匹配,随便找了一道发现完全没有思路……没有看出来是二分图匹配……
思维不够活跃还没有形成套路啊
正式讲一下做法……
每个点看成一条边,拆点,将每个点分别拆成以行,列为标准的两点,遇到#视为换行/列,
再缩点,在一个连通块的点缩为一点。
令以行为标准的点为左边的点,以列为标准的点为右边的点。
(因为对于地图上的点,一个连通块中只能有一个点放炸弹,以此连接行列,
对应二分图中一个点只能有一个匹配,所以可以把这个看做二分图。)
接着匹配即可。
再缩点,在一个连通块的点缩为一点。
令以行为标准的点为左边的点,以列为标准的点为右边的点。
(因为对于地图上的点,一个连通块中只能有一个点放炸弹,以此连接行列,
对应二分图中一个点只能有一个匹配,所以可以把这个看做二分图。)
接着匹配即可。
#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<string> #include<cmath> #include<algorithm> #include<queue> #include<vector> using namespace std; #define Max 205 int n,m,num1=1,num2=1,dis=Max,res=0; char s[100][100]; int x[3000][1300],y[3000][3000],mp[3000][3000],dx[10000],dy[10000],cx[10000],cy[10000],vis[10000],pdx[10000],pdy[10000]; int bfs() { memset(dx,0,sizeof(dx)); memset(dy,0,sizeof(dy)); dis=Max; queue<int> q; for (int i=1;i<=num1;i++) { if (!cx[i]&&pdx[i]) q.push(i); } while (!q.empty()) { int u=q.front(); q.pop(); if (dx[u]>dis) break; for (int i=1;i<=num2;i++) { if (mp[u][i]&&!dy[i]&&pdy[i]) { dy[i]=dx[u]+1; if (!cy[i]) dis=dy[i]; else { dx[cy[i]]=dy[i]+1; q.push(cy[i]); } } } } return dis!=Max; } int find(int u) { for (int i=1;i<=num2;i++) { if (!vis[i]&&mp[u][i]&&dy[i]==dx[u]+1&&pdy[i]) { vis[i]=1; if (cy[i]&&dy[i]==dis) continue; if (!cy[i]||find(cy[i])) { cx[u]=i;cy[i]=u; return 1; } } } return 0; } int main() { scanf("%d%d",&n,&m); for (int i=1;i<=n;i++) scanf("%s",s[i]+1);//输入 for (int i=1;i<=n;i++)//以行为标准 { for (int j=1;j<=m;j++) { if (s[i][j]=='#') num1++;//每个num1相同的点即可缩为一个点 x[i][j]=num1; pdx[num1]=1; } num1++; } for (int j=1;j<=m;j++)//以列为标准 { for (int i=1;i<=n;i++) { if (s[i][j]=='#') num2++;//同上 y[i][j]=num2; pdy[num2]=1; } num2++; } for (int i=1;i<=n;i++) for (int j=1;j<=m;j++) { if(s[i][j]=='*')//连边 { mp[x[i][j]][y[i][j]]=1; //mp[y[i][j]][x[i][j]]=1; } } while (bfs())//二分图匹配,这里用的hopcroft-karp算法,主要想练一下…… { memset(vis,0,sizeof(vis)); for (int i=1;i<=num1;i++) { if (!cx[i]&&pdx[i]) res+=find(i); } } cout<<res; return 0; }