[wikioi 1022]覆盖

在这里首先感谢wjk大神对于我的帮助,没有他,我还很难想出建立二分图的模型。我按照我的个人想法,用最大流写了此题,但是情况不尽人意:


相当牛叉的时间(看最后一个点),惨象已不必多说……

所以,被逼无奈,找到了传说中的匈牙利算法(wjk大神曾称之为“增广路算法”,让我错以为和网络流有关,实际上不是的)

匈牙利算法是可以用来解决二分图匹配问题的,而且效率比最大流高很多……下图就是证据:


下面上神奇的匈牙利算法:

/*
建图方法: 
	把n*m的格子看作是黑白相间
	那么一个黑色格子和一个白色格子匹配在一起了
	就可以看作是被1*2的格子覆盖了
	那么把白色格子看作一个集合
	黑色格子看作一个集合
	就可以进行二分图匹配了
*/
#include<cstdio>
const int maxn = 40100 ;
int n , m , K , tot , T , res = 1;
int head[maxn] , vis[maxn] , lin[maxn] , q[maxn];
int dx[4] = {0 , 0 , -1 , 1} ;
int dy[4] = {1 , -1 , 0 , 0} ;
struct node
{
	int next,v;
}e[maxn] ;
void add(int u,int v) 
{
	e[tot].v = v ;
	e[tot].next = head[u] ;
	head[u] = tot++ ;
}
bool find(int u) 
{
	for(int i = head[u] ; i != -1 ; i = e[i].next) 
	{
		int v = e[i].v ;
		if(vis[v] == T) continue ;
		vis[v] = T ;
		if(!lin[v] || find(lin[v])) 
		{
			lin[v] = u ;
			return true ;
		}
	}
	return false ;
}
int main()
{
	scanf("%d%d%d",&n,&m,&K) ;
	int x1 , y1 ;
	/* 所有的 x*m+y 理解成 坐标(x,y) 所对应的节点的编号就行了 */ 
	for(int i = 0 ; i < K ; i++) 
	{
		scanf("%d%d",&x1,&y1) ;
		x1-- ; y1-- ;
		vis[x1*m+y1] = -1 ; /* vis=-1 表示水塘 */ 
	}
	for(int i = 0 ; i < n ; i++)
	{
		for(int j = (i%2) ; j < m ; j+=2) 
			if(vis[i*m+j] != -1)
			{/* 根据建图原理:这样的双重循环可以区分开二分图中的点 */ 
				q[res] = i*m+j ;
				head[res] = -1;
				for(int k = 0 ; k < 4 ; k++) {
					int nx = i + dx[k] , ny = j + dy[k] ;
					if(nx < 0 || ny < 0 || nx >= n || ny >= m) continue ;
					if(vis[nx*m+ny] == -1) continue ;
					add(res, nx*m+ny) ;
				}
				res++ ;
			}
	}
	int ans = 0 ;
	for(int i = 1 ; i < res ; i++)
	{
		T = i ;
		if(find(i)) ans++ ;
	}
	printf("%d",ans) ;
	return 0 ;
}

再上我SB的最大流:

#include <cstdio>
#include <cstring>
#include <iostream>
#define MaxN 10010
#define MaxM 40010
#define GetP(x,y) ((x)-1)*n+(y)
#define ADD(x,y,c) v[tot]=y,cap[tot]=c,next[tot]=head[x],head[x]=tot++
#define SameSign(x,y) ((x)%2)==((y)%2)
using namespace std;
const int dx[4]={0,0,1,-1};
const int dy[4]={1,-1,0,0};
const int INF=~0u>>2;
int v[MaxM],cap[MaxM],next[MaxM];
int head[MaxN],h[MaxN],num[MaxN];
bool map[110][110];
int n,m,k,MaxFlow,tot,x,y,S,T;
inline void AddEdge(const int &x,const int &y,const int &c)
{
	ADD(x,y,c);
	ADD(y,x,0);
}
inline void BuildDoubleEdge(const int &x,const int &y,const int &c)
{
	AddEdge(x,y,c);
	AddEdge(y,x,c);
}
inline void build(const int &x,const int &y)
{
	if(map[x][y])
		for(int i=0;i<4;i++)
			if(map[x+dx[i]][y+dy[i]])
				BuildDoubleEdge(GetP(x,y),GetP(x+dx[i],y+dy[i]),INF);
}
inline void init()
{
	memset(head,-1,sizeof(head));
	memset(map,true,sizeof(map));
	cin>>n>>m>>k;
	S=n*m+1,T=n*m+2;
	for(int i=0;i<k;i++)
		scanf("%d%d",&x,&y),
		map[x][y]=0;
	for(int i=1;i<=n;i++)
		map[i][0]=map[i][m+1]=0;
	for(int j=1;j<=m;j++)
		map[0][j]=map[n+1][j]=0;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			build(i,j);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			if(map[i][j])
				if(SameSign(i,j))
					AddEdge(S,GetP(i,j),1);
				else
					AddEdge(GetP(i,j),T,1);
	n*=m;	/*Another define of n: tot points*/
	num[0]==++(++n);
}
inline int sap(const int &p,const int &t,const int &in)
{
	if(p==t) return in;
	int out=0,q,flow;
	for(int i=head[p],q=v[i];i!=-1;i=next[i],q=v[i])
		if(cap[i]>0&&h[q]+1==h[p])
		{
			flow=sap(q,t,min(cap[i],in-out));
			cap[i]-=flow;
			cap[i^1]+=flow;
			out+=flow;
			if(in==out) return in;
		}
	if(h[1]<n&&!out)
	{
		if(!--num[h[p]]) h[1]=n;
		num[++h[p]]++;		
	}
	return out;
}
inline void work()
{
	int ans=0;
	while(h[1]<n)
		ans+=sap(S,T,INF);
	cout<<ans<<endl;
}
int main()
{
	init();
	work();
	return 0;
}

所以,二分图匹配最好还是写匈牙利,至于网络流什么的,自有其妙用……

这里在最后引用wjk大神的一句话(对网络流):“只是解决问题变得巧妙了。。效率并不高”……

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值