zoukankan      html  css  js  c++  java
  • [CQOI2012][bzoj2668] 交换棋子 [费用流]

    题面

    传送门

    思路

    抖机灵

    一开始看到这题我以为是棋盘模型-_-||

    然而现实是骨感的

    后来我尝试使用插头dp来交换,然后又惨死

    最后我不得不把目光转向那个总能化腐朽为神奇的算法:网络流

    思维

    我们要先有一个思维的转变:要把棋盘上的“交换”操作,看成所有的黑色棋(白色棋等价)在移动

    我们考虑令一个黑子往下移动一个

    此时当前格子和下方格子的交换数都加一

    考虑一条移动的路径,那么显然,这条路径两端的格子只进行了一次交换,但是路径上的所有格子进行了两次

    我们可以考虑把这个过程变成网络流来做

    但是有一个问题:一个格子如果本来就有一个黑棋,最后没有黑棋,或者本来是白棋,最后是黑棋,那么这个格子的收支会不平衡,也就是说我们硬做,连无向边的时候满足了流量平衡条件但却得不到最优解

    而且如果每个格子只建一个点,也并不能把格子的交换次数限制考虑进去

    那我们就要考虑拆点了

    拆点

    最基础的拆点:一个格子拆成两个,分别代表进入和走出,中间连一条容量为交换次数上限的边

    但是这样有另一个问题:无法体现出路径两端的点和路径中间的点的区别(也就是如果“经过”了一个点,也只统计一点流量)

    那我们再拆:把一个点拆成三个:left,now,right

    从left向now连边、now向right连边,流量上限分别为限制的一半

    这样就完美体现了只有流出、只有流入和流入流出都有的区别

    相邻的点之间从right连向left

    我们令源点向所有初始图中黑棋格子的now连边,汇点跟所有最终图中的黑棋格子的now连边,跑S-T最大流即可

    问题

    第一个大问题:如何解决上文中流量收支可能不平衡的问题?

    答:如果该点是黑子->白子,那么这个点的出一定比入大一点流量;如果是白子->黑子,那么入一定比出大一点流量

    第二个大问题:如何找最小?

    做这个比较好办,把left-now和now-right边增加费用1就好了

    结论&&最终实现方法

    以下用<u,v,w,cap>表示u到v的有向边,费用w流量cap

    建立费用流图,每个点拆成left,now,right

    若该点在初始图中是黑的、最终图中是白的,那么连边(left,now,1,$frac{limit}{2}$),(now,right,1,$frac{limit+1}{2}$)

    若该点在初始图中是白的、最终图中是黑的,那么连边(left,now,1,$frac{limit+1}{2}$),(now,right,1,$frac{limit}{2}$)

    若该点在初始图和最终图中颜色相同,那么连边(left,now,1,$frac{limit}{2}$),(now,right,1,$frac{limit}{2}$)

    其中limit表示这个格子的交换次数上限

    建立附加源汇S-T

    对于初始图中的黑点i,连边(S,now(i),0,1)

    对于最终图中的黑点i,连边(now(i),T,0,1)

    相邻的点i,j之间连边(right(i),left(j),0,inf)

    Code

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define inf 1e9
    #define tot (n*m*3)
    #define left(i,j) ((i-1)*m+j)
    #define now(i,j) (((i-1)*m+j)+n*m)
    #define right(i,j) (((i-1)*m+j)+(n*m<<1))
    using namespace std;
    inline int read(){
    	int re=0,flag=1;char ch=getchar();
    	while(ch>'9'||ch<'0'){
    		if(ch=='-') flag=-1;
    		ch=getchar();
    	}
    	while(ch>='0'&&ch<='9') re=(re<<1)+(re<<3)+ch-'0',ch=getchar();
    	return re*flag;
    }
    const int dx[9]={0,-1,-1,-1,0,0,1,1,1},dy[9]={0,-1,0,1,-1,1,-1,0,1};
    int n,cnt=-1,m,first[2010],dis[2010],vis[2010],ans=0;
    struct edge{
    	int to,next,w,cap;
    }a[50010];
    inline void add(int u,int v,int w,int cap){
    	a[++cnt]=(edge){v,first[u],w,cap};first[u]=cnt;
    	a[++cnt]=(edge){u,first[v],-w,0};first[v]=cnt;
    }
    int q[10010];
    bool spfa(int s,int t){
    	int head=0,tail=1,i,v,u,w;
    	memset(dis,-1,sizeof(dis));memset(vis,0,sizeof(vis));
    	q[0]=t;vis[t]=1;dis[t]=0;
    	while(head<tail){
    		u=q[head++];vis[u]=0;
    		for(i=first[u];~i;i=a[i].next){
    			v=a[i].to;w=a[i].w;
    			if(a[i^1].cap&&((dis[v]==-1)||(dis[v]>dis[u]-w))){
    				dis[v]=dis[u]-w;
    				if(!vis[v]) q[tail++]=v,vis[v]=1;
    			}
    		}
    	}
    	return ~dis[s];
    }
    int dfs(int u,int t,int limit){
    	if(u==t||!limit){vis[u]=1;return limit;}
    	int i,v,f,flow=0,w;vis[u]=1;
    	for(i=first[u];~i;i=a[i].next){
    		v=a[i].to;w=a[i].w;
    		if(!vis[v]&&a[i].cap&&dis[v]==dis[u]-w){
    			if(!(f=dfs(v,t,min(limit,a[i].cap)))) continue;
    			a[i].cap-=f;a[i^1].cap+=f;
    			flow+=f;limit-=f;ans+=w*f;
    			if(!limit) return flow;
    		}
    	}
    	return flow;
    }
    int zkw(int s,int t){
    	int re=0;
    	while(spfa(s,t)){
    		vis[t]=1;
    		while(vis[t]){
    			memset(vis,0,sizeof(vis));
    			re+=dfs(s,t,inf);
    		}
    	}
    	return re;
    }
    int x1[30][30],x2[30][30];
    int main(){
    	memset(first,-1,sizeof(first));
    	int i,j,t1=0,t2=0,ti,tj,k;char s[30];
    	n=read();m=read();
    	for(i=1;i<=n;i++){
    		scanf("%s",s);
    		for(j=1;j<=m;j++){
    			if(s[j-1]=='1'){
    				t1++;add(0,now(i,j),0,1);
    				x1[i][j]=1;
    			}
    		}
    	}
    	for(i=1;i<=n;i++){
    		scanf("%s",s);
    		for(j=1;j<=m;j++){
    			if(s[j-1]=='1'){
    				t2++;add(now(i,j),tot+1,0,1);
    				x2[i][j]=1;
    			}
    		}
    	}
    	if(t1!=t2){
    		puts("-1");return 0;
    	}
    	for(i=1;i<=n;i++){
    		scanf("%s",s);
    		for(j=1;j<=m;j++){
    			t2=s[j-1]-'0';
    			if(x1[i][j]==x2[i][j]) 
    				add(left(i,j),now(i,j),0,t2/2),add(now(i,j),right(i,j),0,t2/2);
    			if(x1[i][j]&&!x2[i][j])
    				add(left(i,j),now(i,j),0,t2/2),add(now(i,j),right(i,j),0,(t2+1)/2);
    			if(!x1[i][j]&&x2[i][j])
    				add(left(i,j),now(i,j),0,(t2+1)/2),add(now(i,j),right(i,j),0,t2/2);
    		}
    	}
    	for(i=1;i<=n;i++){
    		for(j=1;j<=m;j++){
    			for(k=1;k<=8;k++){
    				ti=i+dx[k];tj=j+dy[k];
    				if(ti<1||ti>n||tj<1||tj>m) continue;
    				add(right(i,j),left(ti,tj),1,inf);
    			}
    		}
    	}
    	if(zkw(0,tot+1)!=t1){
    		puts("-1");return 0;
    	}
    	cout<<ans<<endl;
    }
    
  • 相关阅读:
    es5核心技术
    es6 迭代器 和 生成器 学习笔记
    nodejs 基础学习笔记
    node 基本原理
    mac php7 连接数据库遇到的问题
    express ,koa1, koa2学习笔记
    mac mysql的安装
    webpack 给css添加前缀
    利用git将本地的代码同步到github上
    vuex 学习总结及demo
  • 原文地址:https://www.cnblogs.com/dedicatus545/p/8781976.html
Copyright © 2011-2022 走看看