BZOJ 3171 TJOI2013循环格

Problem

BZOJ

Solution

很有意思的一道题目

先讲讲怎么建图:
把每个格子拆成入点 u x u_x ux和出点 v x v_x vx,对于每个格子的入点依次向四周格子的出点连边,方向和给定方向一样则费用为0,否则费用为1。源点向入点连流量为1,费用为0的边,出点向汇点连流量为1,费用为0的边。

那么我们就想要证明每个最大匹配都对应一个完美格子。
其实证明并不难,对于最大匹配下,如果 u x u_x ux的流量流向了 v y v_y vy,我们就在位置 x x x上写下 y y y,那么显然我们会得到一个排列。我们可以把这个看做群论上的一种置换,那么它就可以被拆成一个或多个循环,所以每个格子都可以走回到自己的位置。

Code

#include <assert.h>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <queue>
using namespace std;
typedef long long ll;
const int INF=0x3f3f3f3f;
const int v[4][2]={{-1,0},{0,1},{1,0},{0,-1}};
template <typename Tp> inline int getmin(Tp &x,Tp y){return y<x?x=y,1:0;}
template <typename Tp> inline int getmax(Tp &x,Tp y){return y>x?x=y,1:0;}
template <typename Tp> inline void read(Tp &x)
{
    x=0;int f=0;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') f=1,ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    if(f) x=-x;
}
int n,m,tot,id[20][20];
char s[20][20];
struct data{int v,w,c,nxt;};
struct Mcmf{
	const static int N=510,M=3010;
	int p,S,T,dfc,head[N],dis[N],inq[N],vis[N];
	data edge[M];
	queue<int> q;
	Mcmf(){p=1;S=N-2;T=N-1;}
	void insert(int u,int v,int w,int c)
	{
		edge[++p]=(data){v,w,c,head[u]};head[u]=p;
		edge[++p]=(data){u,0,-c,head[v]};head[v]=p;
	}
	int bfs(int s,int t)
	{
		while(!q.empty()) inq[q.front()]=0,q.pop();
		memset(dis,0x3f,sizeof(dis));
		int x;dis[t]=0;inq[t]=1;q.push(t);
		while(!q.empty())
		{
			x=q.front();q.pop();inq[x]=0;
			for(int i=head[x];i;i=edge[i].nxt)
			  if(edge[i^1].w&&getmin(dis[edge[i].v],dis[x]-edge[i].c))
			  {
			  	if(!inq[edge[i].v]) q.push(edge[i].v),inq[edge[i].v]=1;
			  }
		}
		return dis[s]<INF;
	}
	int dfs(int x,int t,int flow)
	{
		vis[x]=dfc;
		if(x==t||!flow) return flow;
		int rest=0,tmp;
		for(int i=head[x];i;i=edge[i].nxt)
		  if(vis[edge[i].v]^dfc&&edge[i].w&&dis[edge[i].v]==dis[x]-edge[i].c)
		  {
		  	tmp=dfs(edge[i].v,t,min(edge[i].w,flow-rest));
		  	rest+=tmp;edge[i].w-=tmp;edge[i^1].w+=tmp;
		  	if(rest==flow) break;
		  }
		return rest;
	}
	int work()
	{
		int fw=0,fe=0,tmp;
		while(bfs(S,T))
		{
			for(vis[T]=++dfc;vis[T]==dfc;)
			{
				++dfc;tmp=dfs(S,T,INF);
				fw+=tmp;fe+=tmp*dis[S];
			}
		}
		return fe;
	}
}G;
void adj(int &x,int lim){if(x==0) x=lim;if(x>lim) x=1;}
int ch(char x)
{
	if(x=='U') return 0;
	if(x=='R') return 1;
	if(x=='D') return 2;
	if(x=='L') return 3;
	assert(0);
}
void input()
{
	int dir,xx,yy;
	read(n);read(m);tot=n*m;
	for(int i=1;i<=n;i++)
	  for(int j=1;j<=m;j++)
	  {
	  	id[i][j]=(i-1)*m+j;
	  	G.insert(G.S,id[i][j],1,0);
	  	G.insert(id[i][j]+tot,G.T,1,0);
	  }
	for(int i=1;i<=n;i++)
	{
		scanf("%s",s[i]+1);
		for(int j=1;j<=m;j++)
		{
			dir=ch(s[i][j]);
			for(int r=0;r<4;r++)
			{
				xx=i+v[r][0];yy=j+v[r][1];adj(xx,n);adj(yy,m);
				G.insert(id[i][j],id[xx][yy]+tot,1,(dir!=r));
			}
		}
	}
}
int main()
{
	input();
	printf("%d\n",G.work());
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值