牛客练习赛30 D-消消乐(二分图匹配之最小点覆盖)

思路来源

https://blog.csdn.net/wysiwygo/article/details/82710193//原题 poj3041 将问题转化为最小点覆盖

https://blog.csdn.net/niushuai666/article/details/7036897//如何求最小点覆盖是哪些点

https://ac.nowcoder.com/acm/contest/view-submission?submissionId=36885415//代码实现

题解

part1:

图片来自思路来源第一个网址,感觉说的很好,而且原题既视感.jpg

就像点灯问题,以障碍物代边,以行或列代灯,每盏灯点亮连有它的所有边,即消掉所有障碍物。

问题等价于求哪些点构成一个最小点覆盖。

part2:

寻找具体最小点覆盖,从右边未匹配的点出发模拟找增广路,并标记所有其中所有经过的点,

由于已最大匹配,故不可能再找到增广路,所以一定是以未匹配边出发,最后一条边是已匹配边,二者交替。

遇到几条匹配的边即遇到了几个左边匹配的点,余下的即为右边匹配的点,二者之和即为匹配的点数。

故,左边标记的点(左边的点匹配的边)的存在,导致右边的点未能匹配;

右边匹配的点的存在,导致所有左边的未标记的点不能再找到增广路,二者是对称的。

故,这些点构成一个最小点覆盖。

part3:

代码,代码好坑啊调了若干次。

我们用vis[]数组记录是否被访问,在mincover部分记录是否被标记。

cx、cy数组记录该点匹配的点号。

希望以后自己再见到,能再敲出来。

心得

好几个板子,涉及到一个板子不兼容的问题。

令0-(n-1)号点为u集合,其匹配情况的数组cx[]

n-(n+m-1)号点为v集合,其匹配情况的数组cy[]

连边的时候,对v集合加n,

对于那些一个数组维护的,如vis[],如head[],就用原来的点的标号;

对于那些两个数组维护的,如cx[],cy[],超过n的就减n。

代码

#include <iostream>
#include <algorithm> 
#include <cstring>
#include <cstdio>
#include <cmath>
#include <set>
#include <map>
#include <vector>
#include <stack>
#include <queue>
#include <functional>
const int INF=0x3f3f3f3f;
const int maxn=1e5+10; 
const int mod=1e9+7;
const int MOD=998244353;
const double eps=1e-7;
typedef long long ll;
#define vi vector<int> 
#define si set<int>
#define pii pair<double,int> 
#define pi acos(-1.0)
#define pb push_back
#define mp make_pair
#define lowbit(x) (x&(-x))
#define sci(x) scanf("%d",&(x))
#define scll(x) scanf("%lld",&(x))
#define sclf(x) scanf("%lf",&(x))
#define pri(x) printf("%d",(x))
#define rep(i,j,k) for(int i=j;i<=k;++i)
#define per(i,j,k) for(int i=j;i>=k;--i)
#define mem(a,b) memset(a,b,sizeof(a)) 
using namespace std;
int head[4005],cnt,n,m,cx[2005],cy[2005];
bool vis[4005];
char tmp[2005];
struct edge{int to,nex,w;}e[200005];
vector<int>x,y;
void init()
{
	cnt=0;
	mem(head,-1); 
}
void add(int u,int v)
{
	e[cnt].to=v;
	e[cnt].nex=head[u];
	head[u]=cnt++;
}
bool dfs(int u)
{
	vis[u]=1;
	for(int i=head[u];~i;i=e[i].nex)
	{
		int v=e[i].to;
		if(!vis[v])
		{
			vis[v]=1;
			if(cy[v-n]==-1||dfs(cy[v-n]))
			{
				cx[u]=v-n;
				cy[v-n]=u;
				return 1;
			}
		}
	}
	return 0;
}
void mincover()
{
	mem(vis,0);
	rep(i,0,n-1)if(cx[i]==-1)dfs(i);
	rep(i,0,n-1)if(!vis[i])x.push_back(i);
	rep(i,n,n+m-1)if(vis[i])y.push_back(i-n);
}
int hungary()
{
	int res=0;
	mem(vis,0);
	mem(cx,-1);
	mem(cy,-1);
	rep(u,0,n-1)
	{
		mem(vis,0);
		res+=dfs(u);
	}
	return res;
} 
void output()
{
    int len1=x.size(),len2=y.size();
    printf("%d",len1);
    rep(i,0,len1-1)printf(" %d",1+x[i]);
    puts("");
    printf("%d",len2);
    rep(i,0,len2-1)printf(" %d",1+y[i]);
}
int main()
{ 
    init();
    sci(n),sci(m);
    rep(i,0,n-1)
    {
    	scanf("%s",tmp);
    	rep(j,0,m-1)
    	{
    		if(tmp[j]=='*')
    		{
    			add(i,n+j);
    			add(n+j,i);
    		}
    	}
    }
    int ans=hungary();
    printf("%d\n",ans);
    mincover();
    output();
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值