zoukankan      html  css  js  c++  java
  • 洛谷 P3159(BZOJ 2668)[CQOI2012]交换棋子

    有一个(n)(m)列的黑白棋盘,你每次可以交换两个相邻格子(相邻是指有公共边或公共顶点)中的棋子,最终达到目标状态。要求第(i)行第(j)列的格子只能参与(m[i][j])次交换。

    输入格式:

    第一行包含两个整数(n,m(1<=n, m<=20))

    以下(n)行为初始状态,每行为一个包含(m)个字符的(01)串,其中(0)表示黑色棋子,(1)表示白色棋子。

    以下(n)行为目标状态,格式同初始状态。

    以下(n)行每行为一个包含(m)(0-9)数字的字符串,表示每个格子参与交换的次数上限。

    输出格式:

    输出仅一行,为最小交换总次数。如果无解,输出(-1)

    输入样例#1:

    3 3
    110
    000
    001
    000
    110
    100
    222
    222
    222
    

    输出样例#1:

    4
    

    最近一直在学习网络流。写到这个题目的时候,第一反应是:“这怎么可能是网络流呢?”用了一个下午写出来这道题后,感觉其思路实在妙极。

    本题解力求让像我一样初学网络流(初学OI)的人能够看懂,如果还存在疑惑的话欢迎联系我哦~

    看这个题目,很容易想到:可以记录黑色棋子的起始和终结位置,想办法去让棋子从起始位置走到终止位置,一一匹配。棋子在棋盘上走,走的过程中,棋子受到必须成功匹配(最大流)和在此基础上费用最小的约束条件。这样考虑的话,跑费用流自然是再合适不过了。如下图所示:

    进一步考虑会发现,棋子的交换可以被视为在棋盘上的坐标移动。想要连通起始点和终止点,只需要在二者之间建立棋盘的八连通图,让棋子在对应位置上进出棋盘即可。

    但是现在,有一个关键的问题:起始点和终止点作交换的时候, 消耗流量是(1),但是对于中间节点,消耗流量却都应该是(2)。如果单纯的考虑把一个点拆分成一条边的话,无法处理这种边界情况,事情就变得相对比较麻烦。

    回归题目来考虑,题目要求是交换,那么交换就有交换进来和交换出去这两种交换方法。根据这个给我们的灵感,我们可以考虑把一个点拆成(3)个:(inn)(mid)(out),把原本的最大访问量均分在两端上,而把每次的进出流量视为(1)。这样同时又解决了进出棋盘的问题:直接在(mid)处进入棋盘就不用考虑其他麻烦的事情了。

    那么流量均分的想法是否正确呢?基本上是对的。但是,现在我们有了(inn)->(mid)(mid)->(out)两种边,如果边权是奇数,进出棋盘需要的流量只为(1),原本不应该被忽略的零头(1)可能会被忽略或者非最优地分配。

    所以这里又牵涉到了这一点边界的处理问题。如果棋盘开始和结束都有或都没有该棋子,那么我们对可用点权(maxf)(1/2)。否则的话,分别考虑进入和出去的情况:

    可以看到,进入时的(mid)->(out),出去时的(inn)->(mid)会有一条耗流为(1)的边,我们考虑如果这个点不是既进又出节点,就给其存在(1)耗流边的一部分尝试多分配一点"零头"流量(即偶数分配为(n/2)((n+1)/2)都一样,而奇数则分配为(n/2)((n+1)/2)。)

    为了便于各位理清思路,这里本人贴一下建图流程:

    • 初始点->(S) (f=1) (w=0)
    • 最终点<-(T) (f=1) (w=0)
    • 初始点->对应坐标的(mid)节点 (f=1) (w=0)
    • 对应坐标的(mid)节点->终止点 (f=1) (w=0)
    • 棋盘内部的八连通:((out)->(in)) (f=INF) (w=1)
    • (inn)->(mid)(mid)->(out)(w=1),根据情况确认选择 (f=maxf/2) 或者 (f=(maxf+1)/2)

    至此,问题得以完美解决。代码冗长求轻喷。

    #include<queue>
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #define MAXN 2010
    #define MAXM 64010 
    #define INF 0x3f3f3f3f
    #define fpop(x) x.front();x.pop()
    using namespace std;
    
    int pre_node[MAXN],pre_edge[MAXN];
    
    char ch,mp_bg[25][25],mp_ed[25][25];
    
    int n,m,cnt=-1,dis[MAXN],vis[MAXN],flow[MAXN],head[MAXN],maxf[25][25];
    
    struct edge{
    	int nxt,to,w,f;
    }e[MAXM];
    	
    inline int _bg(int x,int y){return n*m*0+(x-1)*m+y;}//起始点[x,y]的编号
    inline int _ed(int x,int y){return n*m*1+(x-1)*m+y;}//目标点[x,y]的编号 
    inline int _inn(int x,int y){return n*m*2+(x-1)*m+y;}//棋盘[x,y]的Inn点编号 
    inline int _mid(int x,int y){return n*m*3+(x-1)*m+y;}//棋盘[x,y]的mid点标号
    inline int _out(int x,int y){return n*m*4+(x-1)*m+y;}//棋盘[x,y]的Out点编号 
    
    inline bool in_map(int x,int y){
    	return 1<=x && x<=n && 1<=y && y<=m;
    }//判断是否越界 
    
    inline void add_edge(int from,int to,int flw,int val){
    	e[++cnt].nxt=head[from];
    	e[cnt].to=to;
    	e[cnt].f=flw;
    	e[cnt].w=val;
    	head[from]=cnt;
    }
    
    queue<int>que;
    
    inline bool spfa(int s,int t){
    	memset(vis,0,sizeof(vis));
    	memset(dis,0x3f,sizeof(dis));
    	memset(flow,0x3f,sizeof(flow));
    	que.push(s); vis[s]=true; dis[s]=0;
    	while(!que.empty()){
    		int u=fpop(que);
    		for(int i=head[u];~i;i=e[i].nxt){
    			int v=e[i].to;
    			if(dis[v]>dis[u]+e[i].w && e[i].f){
    				dis[v]=dis[u]+e[i].w;
    				flow[v]=min(flow[u],e[i].f);
    				pre_node[v]=u;
    				pre_edge[v]=i;
    				vis[v]=true;que.push(v);
    			}
    		}
    	}
    	return dis[t]!=INF;
    }
    
    int mv[8][2]={{1,0},{-1,0},{0,1},{0,-1},{1,1},{1,-1},{-1,1},{-1,-1}};
    
    int main(){
    	memset(head,-1,sizeof(head));
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;++i){
    		for(int j=1;j<=m;++j){
    			scanf(" %c",&mp_bg[i][j]);
    		}
    	}
    	for(int i=1;i<=n;++i){
    		for(int j=1;j<=m;++j){
    			scanf(" %c",&mp_ed[i][j]);
    		}
    	}
    	for(int i=1;i<=n;++i){
    		for(int j=1;j<=m;++j){
    			scanf(" %c",&ch);
    			maxf[i][j]=ch-'0';
    			//最大经过次数 
    		}
    	}
    	//输入起始态和目标态棋盘
    	int s=0,t=n*m*5+1;
    	int cnt_1=0,cnt_2=0;
    	for(int i=1;i<=n;++i){
    		for(int j=1;j<=m;++j){
    			if(mp_bg[i][j]==mp_ed[i][j]){
    				add_edge(_inn(i,j),_mid(i,j),maxf[i][j]/2,0);
    				add_edge(_mid(i,j),_inn(i,j),000000000000,0);
    				
    				add_edge(_mid(i,j),_out(i,j),maxf[i][j]/2,0);
    				add_edge(_out(i,j),_mid(i,j),000000000000,0);
    			}else{
    				if(mp_bg[i][j]=='1'){
    					add_edge(_inn(i,j),_mid(i,j),(maxf[i][j]+0)/2,0);
    					add_edge(_mid(i,j),_inn(i,j),000000000000,0);
    					
    					add_edge(_mid(i,j),_out(i,j),(maxf[i][j]+1)/2,0);
    					add_edge(_out(i,j),_mid(i,j),000000000000,0);
    				}
    				if(mp_ed[i][j]=='1'){
    					add_edge(_inn(i,j),_mid(i,j),(maxf[i][j]+1)/2,0);
    					add_edge(_mid(i,j),_inn(i,j),000000000000,0);
    					
    					add_edge(_mid(i,j),_out(i,j),(maxf[i][j]+0)/2,0);
    					add_edge(_out(i,j),_mid(i,j),000000000000,0);
    				}
    			}
    			
    			if(mp_bg[i][j]=='1'){
    				++cnt_1;
    				//连接源点到初始点     f=1 w=0;
    				add_edge(s,_bg(i,j),1,0);
    				add_edge(_bg(i,j),s,0,0);
    				//连接起始点到棋盘 
    				add_edge(_bg(i,j),_mid(i,j),1,0);
    				add_edge(_mid(i,j),_bg(i,j),0,0);
    			}
    			if(mp_ed[i][j]=='1'){
    				++cnt_2;
    				//连接终结点到汇点     f=1 w=0; 
    				add_edge(_ed(i,j),t,1,0);
    				add_edge(t,_ed(i,j),0,0);
    				//连接棋盘到终结点 
    				add_edge(_mid(i,j),_ed(i,j),1,0);
    				add_edge(_ed(i,j),_mid(i,j),0,0);
    			}
    			//棋盘的八连通边  f=INF w=1;
    			for(int k=0;k<8;++k){
    				int ni=i+mv[k][0];
    				int nj=j+mv[k][1];
    				if(in_map(ni,nj)){
    					//从点[i,j]的out连接点[ni,nj]的inn 
    					add_edge(_out(i,j),_inn(ni,nj),INF,+1);
    					add_edge(_inn(ni,nj),_out(i,j),000,-1);
    				}
    			}
    		}
    	}
    	//棋子数变动->No solution 
    	if(cnt_1!=cnt_2){
    		puts("-1");
    		return 0;
    	}
    	//然后跑费用流 
    	int max_flow=0,min_cost=0;
    	while(spfa(s,t)){
    		max_flow+=flow[t];
    		min_cost+=flow[t]*dis[t];
    		int u=t;
    		while(u!=s){
    			e[pre_edge[u]^0].f-=flow[t];
    			e[pre_edge[u]^1].f+=flow[t];
    			u=pre_node[u];
    		}
    	}
    	if(max_flow!=cnt_1){
    		puts("-1");
    		return 0;
    	}
    	printf("%d
    ",min_cost);
    }
    
    
  • 相关阅读:
    alpha测试和beta测试的区别
    当设计师遭遇HTML5
    软件开发项目中如何进行风险管理
    程序员应知——关注细节
    与Janet关于敏捷测试若干问题的Q&A
    软件开发中的哲学——世界的本原是物质(一)
    软件开发中的哲学——写在前面
    软硬兼施让客户满意
    浅谈DBA的角色以及与业务的关系
    连接access时的REGDB_E_CLASSNOTREG(0x80040154)错误
  • 原文地址:https://www.cnblogs.com/maomao9173/p/9971283.html
Copyright © 2011-2022 走看看