[BZOJ3171/Luogu3965][TJOI2013]循环格

题目链接:

BZOJ3171

Luogu3965

首先,有一个很显然的结论,每一个格点都要有且只有一条入边,因为整张图有\(r*c\)个点和边,每个点都要有入边。

现在要平均分配每一条边,次数最小,那么就很简单了,费用流。

把每个点拆成入点和出点,对于每个入点,和源点连边,容量\(1\),费用\(0\),表示可以向其他点贡献入边。

对于每个点向四周连边,容量为\(1\)(可以向四周连边),若方向与箭头相同费用为\(0\)(不需要改变),否则为\(1\)(需要修改)。

对于每个出点向汇点连边,容量\(1\),费用\(0\)(得到了一条入边)。

#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ID(x,y,z) ((z)*n*m+((x)-1)*m+(y))

inline int Min(int a,int b){return a<b?a:b;}
inline int Max(int a,int b){return a>b?a:b;}

int n,m,St,Ed,MaxFlow,MinCost;
int Head[505],Next[6005],To[6005],Val[6005],Cos[6005],En=1;
int Pre[505],Ref[505],Dis[505];
char Grid[20];
bool Inq[505];
const int nx[]={-1,1,0,0},ny[]={0,0,-1,1};
const char s[]={'U','D','L','R'};

inline void Add(int x,int y,int z,int w)
{
	Next[++En]=Head[x],To[Head[x]=En]=y,Val[En]=z,Cos[En]=+w;
	Next[++En]=Head[y],To[Head[y]=En]=x,Val[En]=0,Cos[En]=-w;
}

bool SPFA()//压行费用流,不要在意
{
	std::queue<int> q;
	memset(Dis,0x3f,sizeof Dis);
	q.push(St),Dis[St]=0,Ref[St]=1<<30;
	for(int x,y;!q.empty();q.pop(),Inq[x]=false)
		for(int i=Head[x=q.front()];i;i=Next[i])
			if(Val[i]&&Dis[y=To[i]]>Dis[x]+Cos[i])
			{
				Dis[y]=Dis[x]+Cos[Pre[y]=i];
				Ref[y]=Min(Ref[x],Val[i]);
				if(!Inq[y])q.push(y),Inq[y]=true;
			}
	if(Dis[Ed]==0x3f3f3f3f)return false;
	MaxFlow+=Ref[Ed],MinCost+=Ref[Ed]*Dis[Ed];
	for(int x=Ed,i;x!=St;x=To[i^1])
		Val[i=Pre[x]]-=Ref[Ed],Val[i^1]+=Ref[Ed];
	return true;
}

int main()
{
	scanf("%d%d",&n,&m),St=n*m<<1|1,Ed=St+1;
	for(int i=1;i<=n;++i)
	{
		scanf("%s",Grid+1);
		for(int j=1;j<=m;++j)
		{
			int t=std::find(s,s+4,Grid[j])-s;
			Add(St,ID(i,j,0),1,0);
			Add(ID(i,j,1),Ed,1,0);//向源点与汇点连边
			for(int k=0;k<4;++k)
			{
				int wx=i+nx[k],wy=j+ny[k];//箭头方向
				if(!wx)wx=n;
				else if(wx>n)wx=1;
				if(!wy)wy=m;
				else if(wy>m)wy=1;//边界处理
				Add(ID(i,j,0),ID(wx,wy,1),1,k!=t);
			}
		}
	}
	while(SPFA());
	printf("%d\n",MinCost);
	return 0;
}
posted @ 2019-01-02 15:15  LanrTabe  阅读(98)  评论(0编辑  收藏  举报