bzoj3171【tjoi2013】循环格

3171: [Tjoi2013]循环格

Time Limit: 1 Sec   Memory Limit: 128 MB
Submit: 628   Solved: 381
[ Submit][ Status][ Discuss]

Description

一个循环格就是一个矩阵,其中所有元素为箭头,指向相邻四个格子。每个元素有一个坐标(行,列),其中左上角元素坐标为(0,0)。给定一个起始位置(r,c)

,你可以沿着箭头防线在格子间行走。即如果(r,c)是一个左箭头,那么走到(r,c-1);如果是右箭头那么走到(r,c+1);如果是上箭头那么走到(r-1,c);如果是下箭头那么走到(r+1,c);每一行和每一列都是循环的,即如果走出边界,你会出现在另一侧。
一个完美的循环格是这样定义的:对于任意一个起始位置,你都可以i沿着箭头最终回到起始位置。如果一个循环格不满足完美,你可以随意修改任意一个元素的箭头直到完美。
给定一个循环格,你需要计算最少需要修改多少个元素使其完美。

Input

第一行两个整数R,C。表示行和列,接下来R行,每行C个字符LRUD,表示左右上下。

Output

一个整数,表示最少需要修改多少个元素使得给定的循环格完美

Sample Input

3 4
RRRD
URLL
LRRR

Sample Output

2

HINT

1<=R,L<=15

Source





首先我们考虑要使从一个点出发最后回到起点,只需每一个点的入度和出度都为1,所以想到了网络流。所以把每一个点拆分为入点和出点,从源点到所有入点连一条流量为1、费用为0的边,再从所有出点到汇点连一条流量为1、费用为0的边。然后将每个点的入点向它上下左右的点的出点连一条边,流量为1,如果与原有方向相同则费用为0,否则费用为1。最后在图中求最小费用最大流

另外需要注意的是,最小费用最大流算法中要注意cnt初值为1,这样才可以保证在求反向边时可以用异或操作实现。

P.S. 开数组时大胆开,空间什么都是浮云,你看看数据才多少!?




#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#define F(i,j,n) for(int i=j;i<=n;i++)
#define D(i,j,n) for(int i=j;i>=n;i--)
#define LL long long
#define INF 1000000000
using namespace std;
struct edge_type
{
	int next,from,to,f,c;
}e[20500];
int r,c,x,y,xx,yy,cnt=1,s,t,ans,dx[4]={0,0,-1,1},dy[4]={-1,1,0,0};
int dis[500],f[500][500],head[500],pre[500];
bool inq[500];
char ch;
queue<int>q;
void add_edge(int a,int b,int flow,int cost)
{
	e[++cnt]=(edge_type){head[a],a,b,flow,cost};head[a]=cnt;
	e[++cnt]=(edge_type){head[b],b,a,0,-cost};head[b]=cnt;
}
bool spfa()
{
	memset(inq,false,sizeof(inq));
	F(i,0,499) dis[i]=INF;
	dis[s]=0;
	inq[s]=true;
	q.push(s);
	while (!q.empty())
	{
		int tmp=q.front();
		q.pop();
		inq[tmp]=false;
		for (int p=head[tmp];p;p=e[p].next)
		{
			if (e[p].f&&dis[tmp]+e[p].c<dis[e[p].to])
			{
				dis[e[p].to]=dis[tmp]+e[p].c;
				pre[e[p].to]=p;
				if (!inq[e[p].to])
				{
					q.push(e[p].to);
					inq[e[p].to]=true;
				}
			}
		}
	}
	return dis[t]!=INF;
}
int main()
{
	ans=0;
	memset(head,0,sizeof(head));
	F(i,0,249) F(j,0,249) f[i][j]=1;
	scanf("%d%d",&r,&c);
	F(i,0,r-1) 
	{
		F(j,0,c-1)
		{
			ch=getchar();
			while (ch!='L'&&ch!='R'&&ch!='U'&&ch!='D') ch=getchar();
			if (ch=='L'){x=i;y=j-1;}
			if (ch=='R'){x=i;y=j+1;}
			if (ch=='U'){x=i-1;y=j;}
			if (ch=='D'){x=i+1;y=j;}
			x=(x+r)%r;
			y=(y+c)%c;
			f[i*c+j][x*c+y]=0;
			F(k,0,3)
			{
				xx=(i+dx[k]+r)%r;
				yy=(j+dy[k]+c)%c;
				add_edge(i*c+j,xx*c+yy+r*c,1,f[i*c+j][xx*c+yy]);
			}
		}
	}
	s=498;
	t=499;
	F(i,0,r*c-1)
	{
		add_edge(s,i,1,0);
		add_edge(i+r*c,t,1,0);
	}
	while (spfa())
	{
		int mf=INF;
		for(int p=pre[t];p;p=pre[e[p].from]) mf=min(mf,e[p].f);
		ans+=mf*dis[t];
		for(int p=pre[t];p;p=pre[e[p].from])
		{
			e[p].f-=mf;
			e[p^1].f+=mf;
		}
	}
	printf("%d\n",ans);
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值