#网络流,费用流#洛谷 3159 JZOJ 2765 交换棋子

费用流算法解析
本文深入探讨了费用流算法在解决黑白棋盘交换问题中的应用,通过详细的代码解析和算法流程说明,展示了如何从构建图模型到实现最短路径算法,最终求解最少交换次数到达目标状态的过程。

题目

给出一个黑白棋盘的起始状态,每个棋子可以向相邻或对角相邻的棋子交换,但是有限定的交换次数,问最少交换多少次到达目标状态


分析

一开始的思路就是跑费用流,
但是单纯的建图貌似不对,因为如果直接拆点会中间点交换两次,那么可以把次数取一半,对于原先是黑棋子而最后是白棋子向源点连边,原白后黑向汇点连边


代码

    #include <cstdio>
    #include <cstring>
    #include <queue>
    #define rr register
    #define id(x,y) (x-1)*m+y
    #define r(i,a,b) for (rr int i=a;i<=b;i++)
    using namespace std;
    const int dx[8]={0,0,1,1,1,-1,-1,-1},dy[8]={1,-1,1,0,-1,1,0,-1};
    struct node{int y,w,f,next;}e[8001]; bool v[805];
    int n,m,s,t,p[21],o[21],ls[805],tot,tot1,k=1,ans,dis[805];
    inline void add(int x,int y,int w,int f){
    	e[++k]=(node){y,w,f,ls[x]}; ls[x]=k;
    	e[++k]=(node){x,0,-f,ls[y]};ls[y]=k;
    }
    inline bool spfa(){
        memset(v,0,sizeof(v)); memset(dis,127/3,sizeof(dis));
        dis[t]=0; v[t]=1; rr deque<int>q; q.push_back(t);
        while (q.size()){
            rr int x=q.front(); q.pop_front();
            for (rr int i=ls[x];i;i=e[i].next)
            if (e[i^1].w&&dis[e[i].y]>dis[x]-e[i].f){
                dis[e[i].y]=dis[x]-e[i].f;
                if (!v[e[i].y]){
                    v[e[i].y]=1;
                    if (q.size()&&dis[e[i].y]<dis[q.front()]) q.push_front(e[i].y); else q.push_back(e[i].y);
                }
            }
            v[x]=0;
        }
        return dis[s]<707406378;
    }
    inline int dfs(int x,int now){
        if (x==t) {v[t]=1; return now;}
        rr int rest=0,f; v[x]=1;
        for (rr int i=ls[x];i;i=e[i].next)
        if (!v[e[i].y]&&e[i].w&&dis[e[i].y]+e[i].f==dis[x]){
            rest+=(f=dfs(e[i].y,min(e[i].w,now-rest)));
            if (f) ans+=f*e[i].f,e[i].w-=f,e[i^1].w+=f;
            if (rest==now) break;
        }
        return rest;
    }
    int main(){
    	scanf("%d%d",&n,&m); s=n*m<<1|1; t=s+1;
    	r(i,1,n) r(j,1,m){
    		char c=getchar();
    		while (c<48||c>57) c=getchar();
    		p[i]=p[i]<<1|(c-'0'); tot+=c-'0';
    	}
    	r(i,1,n) r(j,1,m){
    		char c=getchar();
    		while (c<48||c>57) c=getchar();
    		o[i]=o[i]<<1|(c-'0'); tot1+=c-'0';//状压
    	}
    	if (tot!=tot1) return !printf("-1"); tot=0;//一号剪枝,无论怎么跑都没办法
    	r(i,1,n) r(j,1,m){
    		char c=getchar();
    		while (c<48||c>57) c=getchar();
    		if ((p[i]&(1<<m-j))&&!(o[i]&(1<<m-j))) {
    			add(s,id(i,j),1,0),tot++;
    			if ((c-'0')&1) add(id(i,j),id(i+n,j),1,0);//如果少了1要补回去
    		}
    		if (!(p[i]&(1<<m-j))&&(o[i]&(1<<m-j))){
    			add(id(i+n,j),t,1,0);
    			if ((c-'0')&1) add(id(i,j),id(i+n,j),1,0);
    		}
    		add(id(i,j),id(i+n,j),(c-'0')>>1,0);
    		r(t1,0,7){
    			rr int x=i+dx[t1],y=j+dy[t1];
    			if (!x||!y||x>n||y>m) continue;
    			add(id(i+n,j),id(x,y),23333333,1);//需要交换一次
    		}
    	}
    	rr int t1=0;
    	while (spfa()){//zkw费用流
    		do{
    			memset(v,0,sizeof(v));
    			t1+=dfs(s,1e9);
    		}while (v[t]);
    	}
    	if (t1!=tot) return !printf("-1");//不可能
    	    else return !printf("%d",ans);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值