版权声明:本文为博主原创文章,你们可以随便转载 https://blog.csdn.net/Jaihk662/article/details/83662773
链接:https://ac.nowcoder.com/acm/contest/216/D
来源:牛客网
题目描述
r神在和小b比赛玩一个名为“消消乐”的游戏,在一个n*m的棋盘上,一些棋子分布在格点上,游戏玩家有一个名为超蓝光波的武器,可以消除一行或者一列的所有棋子,使用超蓝光波需要耗费一点能量,消除完所有的棋子之后,花费能量越少得分越高。
r神为了超过排名第一的小b,夺得荣誉称号“天下第一”,他需要寻求你的帮助,他希望知道最少需要使用多少次“超蓝光波”,以及在哪行、哪列使用。
输入描述:
第一行两个正整数n(n<=2000),m(m<=2000);
接下来n行,每行m个字符,表示棋盘,其中“.”表示该处没有棋子,“*”表示该处有棋子,棋子个数<=100000
输出描述:
第一行输出一个正整数,表示最少需要使用的“超蓝光波”次数
第二行N+1个正整数,第一个数为N,表示需要消掉的行数,从小到大输出每个需要消除的行号
第三行M+1个正整数,第一个数为M,表示需要消掉的列数,从小到大输出每个需要删除的列号
如果有多种情况,任意输出一种即可。
输入
3 4
.*..
**.*
.*..
输出
2
1 2
1 2
很容易发现这题就是个二分匹配
如果(x, y)上有棋子,那么第x行向第y列连一条边
求出最小点覆盖就是答案
然而这题还需要输出一种最小点覆盖方案
二分匹配时一些变量如下
- visx[p]/visy[p]:左边/右边第p个点是否被标记
- L[p]/R[p]:最大匹配下,左边/右边第p个点连接了对面的哪个点(如果这个点没有与任何点相连,为-1)
步骤如下:
- 先进行一次二分匹配
- 清空visx[]和visy[],并对左边所有没有匹配成功的点,再次尝试匹配
- 这个时候所有visx[]为0,visy[]不为0的点即最小点覆盖中的点
#include<stdio.h>
#include<string.h>
#include<vector>
using namespace std;
char str[2005];
int k, n, m, visx[2005], visy[2005], L[2005], R[2005];
vector<int> G[2005], F;
int Sech(int x)
{
int i, v;
visx[x] = 1;
for(i=0;i<G[x].size();i++)
{
v = G[x][i];
if(visy[v]==0)
{
visy[v] = 1;
if(R[v]==-1 || Sech(R[v]))
{
R[v] = x, L[x] = v;
return 1;
}
}
}
return 0;
}
int main(void)
{
int i, j, ans;
scanf("%d%d", &n, &m);
for(i=1;i<=n;i++)
{
scanf("%s", str+1);
for(j=1;j<=m;j++)
{
if(str[j]=='*')
G[i].push_back(j);
}
}
ans = 0;
memset(L, -1, sizeof(L));
memset(R, -1, sizeof(R));
for(i=1;i<=n;i++)
{
memset(visy, 0, sizeof(visy));
if(Sech(i))
ans++;
}
memset(visx, 0, sizeof(visx));
memset(visy, 0, sizeof(visy));
printf("%d\n", ans);
for(i=1;i<=n;i++)
{
if(L[i]==-1)
Sech(i);
}
for(i=1;i<=n;i++)
{
if(visx[i]==0)
F.push_back(i);
}
printf("%d", F.size());
for(i=0;i<F.size();i++)
printf(" %d", F[i]);
puts("");
F.clear();
for(i=1;i<=m;i++)
{
if(visy[i])
F.push_back(i);
}
printf("%d", F.size());
for(i=0;i<F.size();i++)
printf(" %d", F[i]);
puts("");
return 0;
}