在这里首先感谢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大神的一句话(对网络流):“只是解决问题变得巧妙了。。效率并不高”……