[CQOI2012]交换棋子(拆点+费用流)

题目

(注意相邻交换指的是八连通)

思路

显然可以将黑白交换看做只有黑色棋子在空棋盘上移动

交换限制交给流量,那么有代价自然就是费用流了

考虑直接将两个相邻格子连起来,由于交换次数是相对单个格子而言的,流量无法确定
既然交换次数是单个格子的属性,自然可以考虑到拆点来表示其属性

考虑将一个格子拆成入点和出点,中间连一条边,流量为交换次数;但是想象一个黑色棋子从1->2->3,那么中间格子用了两次,两边格子只用了一次,这个路径就没法用两个点表示了

继续拆,拆成三个点,入点出点和中间点,中间点即为黑色格子所在位置,移动操作相当于中间点->出点->入点->中间点;我们在中间点->出点,入点->中间点这两个地方限流;
发现如果一个位置初始是黑点,最终是白点,那么出去的流量显然为进来的流量+1,其余同理

所以将交换次数平分给进来和出去两个地方,分情况讨论“1”的分配

其他的边很好想,连好后跑一次MCMF即可

#include<bits/stdc++.h>
#define N 2005
#define M 200005
using namespace std;
const int inf = 100000000;
int n,m,s,t,maxflow,mincost;
int pre[N],preedge[N],flow[N];
int exist[N],dis[N];
int dox[8]={-1,-1,-1,0,0,1,1,1};
int doy[8]={-1,0,1,-1,1,-1,0,1};
char st[25][25],ed[25][25],lim[25][25];
struct Edge
{
	int next,to,flow,dis;
}edge[M<<1];int head[N],cnt=1;
void add_edge(int from,int to,int flow,int dis)
{
	edge[++cnt].next=head[from];
	edge[cnt].to=to;
	edge[cnt].flow=flow;
	edge[cnt].dis=dis;
	head[from]=cnt;
}
void add(int from,int to,int flow,int dis)
{
	add_edge(from,to,flow,dis);
	add_edge(to,from,0,-dis);
}
int get_id(int x,int y,int c) { return n*m*c + (x-1)*m + y; }
bool spfa()
{
	memset(dis,50,sizeof(dis));
	memset(flow,50,sizeof(flow));
	queue<int> q; 
	dis[s]=0; q.push(s); exist[s]=1;
	pre[t]=-1;
	while(!q.empty())
	{
		int u=q.front(); q.pop(); exist[u]=0;
		for(int i=head[u];i;i=edge[i].next)
		{
			int v=edge[i].to;
			if(edge[i].flow && dis[v]>dis[u]+edge[i].dis)
			{
				pre[v]=u;
				preedge[v]=i;
				dis[v]=dis[u]+edge[i].dis;
				flow[v]=min(flow[u],edge[i].flow);
				if(!exist[v]) {exist[v]=1;q.push(v);} 
			}
		}
	}
	return (pre[t] != -1);
}
void MCMF()
{
	while(spfa())
	{
		maxflow += flow[t];
		mincost += dis[t]*flow[t];
		int now=t;
		while(now!=s)
		{
			edge[preedge[now]].flow-=flow[t];
			edge[preedge[now]^1].flow+=flow[t];
			now=pre[now];
		}
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	s=0; t=N-1;
	for(int i=1;i<=n;++i) scanf("%s",st[i]);
	for(int i=1;i<=n;++i) scanf("%s",ed[i]);
	for(int i=1;i<=n;++i) scanf("%s",lim[i]);
	int sum=0;
	for(int i=1;i<=n;++i)
	{
		for(int j=1;j<=m;++j)
		{
			//入点出点 
			if(st[i][j-1]=='1') ++sum,add(s,get_id(i,j,1),1,0);
			if(ed[i][j-1]=='1') add(get_id(i,j,1),t,1,0);
			//自己连边
			int l = (lim[i][j-1]-'0');
			if(st[i][j-1]=='1' && ed[i][j-1]=='0')
			{
				add(get_id(i,j,0),get_id(i,j,1),l/2,1);
				add(get_id(i,j,1),get_id(i,j,2),(l+1)/2,1);
			}
			else if(st[i][j-1]=='0' && ed[i][j-1]=='1')
			{
				add(get_id(i,j,0),get_id(i,j,1),(l+1)/2,1);
				add(get_id(i,j,1),get_id(i,j,2),l/2,1);
			}
			else 
			{
				add(get_id(i,j,0),get_id(i,j,1),l/2,1);
				add(get_id(i,j,1),get_id(i,j,2),l/2,1);
			}
			//八连通 
			for(int k=0;k<8;++k)
			{
				int dx=dox[k]+i,dy=doy[k]+j;
				if(dx<1 || dx>n || dy<1 || dy>m) continue;
				add(get_id(i,j,2),get_id(dx,dy,0),inf,0);
			}
		}
	}
	MCMF();
	if(maxflow != sum) cout<<-1<<endl;
	else cout<<mincost/2<<endl;
	return 0;
}
原文地址:https://www.cnblogs.com/Chtholly/p/11990205.html