【CQOI2006】移动棋子(BSOI1157)

【CQOI2006】移动棋子

Description

在一个n*n的棋盘上有n枚棋子。每次可以把一枚棋子往上、下、左、右方向之一移动一格,最后排成一行、一列或者主、副对角线上(因此一共有2n+2条可能的目标状态),要求移动次数最小。
棋盘上有一些位置是障碍,棋子在任何时候都不能经过。棋子的初始位置保证不在障碍物上。任两枚棋子不能在同时到达同一个格子。

Input

第一行包含两个整数n, m,表示棋子的个数(它也是棋盘的边长)和障碍的个数。以下n行,每行两个整数(x, y),表示第i个棋子的坐标(1<=x, y<=n),以下m行,每行给出一个障碍物的坐标。假设这n+m个坐标两两不重合。
【限制】
50%的数据满足:2<=n<=15,m=0
100%的数据满足:2<=n<=50, 0<=m<=100

Output

仅包含一个整数,表示最小移动步数。如果无解,输出-1。

Sample Input

5 1

1 2

2 4

3 4

5 1

5 3

1 1

Sample Output

6

Solution

        在看到这道题之后,通过对棋盘与小数据这两个特征,蒟蒻猜出了这是一道费用流。

        不难发现,符合题意的结束状态共有2*n+2种,即填满一行、一列或者任意一条对角线。

        那么很明显,我们要连续做2*n+2次费用流了。我们将源点向每一个棋子连流量为1,费用为0的边,相同的边也从每一次的结束状态向汇点连边。接下来亟待解决的就是从每一个棋子向每一个结束位置的连边了。

        因为所有的结束状态覆盖了棋盘的每一个点,那么我们可以用bfs预先处理出棋子到每一个点的距离,也就是费用了,障碍物的距离为inf。

CODE

#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
const int inf=0x3f3f3f3f;
struct Pipe {int next,to,flow,cost;}pipe[50005];
int h[505],cnt=1;
inline void add(int x,int y,int z,int k){
	pipe[++cnt].to=y;pipe[cnt].next=h[x];h[x]=cnt;
	pipe[cnt].flow=z;pipe[cnt].cost=k;
	pipe[++cnt].to=x;pipe[cnt].next=h[y];h[y]=cnt;
	pipe[cnt].flow=0;pipe[cnt].cost=-k;
	return ;
}
inline int read(){
	char c;int rec=0;
	while((c=getchar())<'0'||c>'9');
	while(c>='0'&&c<='9')rec=rec*10+c-'0',c=getchar();
	return rec;
}
int n,m,ans=inf,sum,tot;
int map[55][55];
int chess[55][2],rock[105][2];
void into(){
	int i,j;
	for(i=1;i<=n;i++){chess[i][0]=read();chess[i][1]=read();}
	for(i=1;i<=m;i++){rock[i][0]=read();rock[i][1]=read();map[rock[i][0]][rock[i][1]]=-1;}
	return ;
}
int Move[55][55][55];
int vis[55][55],rec[55][55];
int dx[4]={0,0,-1,1};
int dy[4]={1,-1,0,0};
int q[3005][2];
void prepare(){
	int i,j,k;
	int head,tail;
	for(i=1;i<=n;i++){
		memset(vis,0,sizeof(vis));
		memset(rec,0,sizeof(rec));
		memset(q,0,sizeof(q));
		head=1;tail=1;
		q[head][0]=chess[i][0];q[head][1]=chess[i][1];vis[chess[i][0]][chess[i][1]]=1;
		rec[chess[i][0]][chess[i][1]]=0;head=0;
		while(head<tail){
			head++;
			for(j=0;j<4;j++){
				int x=q[head][0]+dx[j],y=q[head][1]+dy[j];
				if(x<0||x>n||y<0||y>n||vis[x][y])continue;
				vis[x][y]=1;if(map[x][y]==-1){rec[x][y]=inf;continue;}
				rec[x][y]=rec[q[head][0]][q[head][1]]+1;
				q[++tail][0]=x;q[tail][1]=y;
			}
		}
		for(j=1;j<=n;j++)
			for(k=1;k<=n;k++)Move[i][j][k]=rec[j][k];
	}
	return ;
}
inline void Clean(){cnt=1;memset(h,0,sizeof(h));sum=0;tot=n;return ;}
int st,ed;
int goal[55][2];
int dis[505],Vis[505],prep[505],pree[505];
inline bool SPFA(int st,int ed){
	int i,j,p;
	queue<int> q;
	memset(dis,0x3f,sizeof(dis));
	memset(Vis,0,sizeof(Vis));
	q.push(st);Vis[st]=1;dis[st]=0;
	while(!q.empty()){
		p=q.front();q.pop();Vis[p]=0;
		for(i=h[p];i;i=pipe[i].next){
			j=pipe[i].to;
			if(pipe[i].flow&&dis[j]>dis[p]+pipe[i].cost){
				dis[j]=dis[p]+pipe[i].cost;
				prep[j]=p;pree[j]=i;
				if(!Vis[j]){q.push(j);Vis[j]=1;}
			}
		}
	}
	return dis[ed]<inf;
}
inline void Adjust(){
	int i,j,p=inf;
	for(i=ed;i!=st;i=prep[i]){
		j=pree[i];
		p=min(p,pipe[j].flow);
	}
	for(i=ed;i!=st;i=prep[i]){
		j=pree[i];
		pipe[j].flow-=p;pipe[j^1].flow+=p;
	}
	sum+=p*dis[ed];
	return ;
}
int main(){
	n=read();m=read();
	into();st=0;ed=2*n+1;
	prepare();
	int i,j,k,f,ff;
	for(i=1;i<=n;i++){
	    f=0;
		Clean();
		for(j=1;j<=n;j++){add(st,j,1,0);add(j+n,ed,1,0);}
		for(j=1;j<=n;j++){goal[j][0]=i;goal[j][1]=j;if(map[i][j]==-1){f=1;break;}}
		if(f)continue;
		for(j=1;j<=n;j++)
			for(k=1;k<=n;k++)
				add(j,k+n,inf,Move[j][goal[k][0]][goal[k][1]]);
		while(SPFA(st,ed))Adjust(),tot--;
		if(!tot)ans=min(ans,sum);
		Clean();
		for(j=1;j<=n;j++){add(st,j,1,0);add(j+n,ed,1,0);}
		for(j=1;j<=n;j++){goal[j][0]=j;goal[j][1]=i;if(map[j][i]==-1){f=1;break;}}
		if(f)continue;
		for(j=1;j<=n;j++)
			for(k=1;k<=n;k++)
				add(j,k+n,inf,Move[j][goal[k][0]][goal[k][1]]);
		while(SPFA(st,ed))Adjust(),tot--;
		if(!tot)ans=min(ans,sum);
	}
	f=0;
	Clean();
	for(i=1;i<=n;i++){add(st,i,1,0);add(i+n,ed,1,0);}
	for(i=1;i<=n;i++){goal[i][0]=i;goal[i][1]=i;if(map[i][i]==-1)f=1;}
	for(i=1;i<=n;i++){
		for(j=1;j<=n;j++)
			add(i,j+n,inf,Move[i][goal[j][0]][goal[j][1]]);
	}
	while(SPFA(st,ed))Adjust(),tot--;
	if(!f&&!tot)ans=min(ans,sum);
	f=0;
	Clean();
	for(i=1;i<=n;i++){add(st,i,1,0);add(i+n,ed,1,0);}
	for(i=1;i<=n;i++){goal[i][0]=n-i+1;goal[i][1]=n-i+1;if(map[n-i+1][n-i+1]==-1)f=1;}
	for(i=1;i<=n;i++)
		for(j=1;j<=n;j++)
			add(i,j+n,inf,Move[i][goal[j][0]][goal[j][1]]);
	while(SPFA(st,ed))Adjust(),tot--;
    if(!f&&!tot)ans=min(ans,sum);
	cout<<ans;
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值