[CQOI2012]交换棋子 拆成3点的费用流

题目

[CQOI2012]交换棋子(https://ac.nowcoder.com/acm/problem/19921)

题目描述

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

输入描述:

第一行包含两个整数n,m(1 ≤ n, m ≤ 20)。
以下n行为初始状态,每行为一个包含m个字符的01串,其中0表示黑色棋子,1表示白色棋子。
以下n行为目标状态,格式同初始状态。
以下n行每行为一个包含m个0~9数字的字符串,表示每个格子参与交换的次数上限。

输出描述:

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

输入

3 3
110
000
001
000
110
100
222
222
222

输出

4

思路

#pragma GCC optimize(3, "Ofast", "inline")
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const LL maxn=1400;
const LL maxm=1e6+10;

struct Mcmf_flow{
    bool vis[maxn];
    LL dis[maxn];
    LL pre[maxn];
    LL last[maxn];
    LL flow[maxn];
    LL zdl, fy;
    LL s1[maxn];//sum的前缀和
    LL sum[maxn];//流量为i的最小费用
    LL tot=0;
    //dis最小花费;pre每个点的前驱;last每个点的所连的前一条边;flow源点到此处的流量
    struct Edge {
        LL to,next,flow,dis;//flow流量 dis花费
    } e[maxm<<1];
    LL head[maxn],cut;
    queue <LL> q;
    int N=0;
    void init(int n){
        N=n+5;
        memset(s1, 0, sizeof(LL)*(N+5));
        memset(sum, 0, sizeof(LL)*(N+5));
        memset(head,-1,sizeof(LL)*(N+5));
        tot=0;
        cut=-1;//初始化
        zdl=fy=0;
    }

    void add_e(LL from,LL to,LL flow,LL dis) {
        e[++cut].next=head[from];
        e[cut].to=to;
        e[cut].flow=flow;
        e[cut].dis=dis;
        head[from]=cut;
    }

    void add(LL x, LL y, LL z, LL f){
        add_e(x,y,z,f);
        add_e(y,x,0,-f);
    }

    bool spfa(LL s,LL t) {
        memset(dis,0x7f,sizeof(LL)*(N+5));
        memset(flow,0x7f,sizeof(LL)*(N+5));
        memset(vis,0,sizeof(bool)*(N+5));
        q.push(s);
        vis[s]=1; dis[s]=0; pre[t]=-1;
        while (!q.empty()) {
            LL now=q.front();
            q.pop(); vis[now]=0;
            for (LL i=head[now]; i!=-1; i=e[i].next) {
                if (e[i].flow>0 && dis[e[i].to]>dis[now]+e[i].dis) { //正边
                    dis[e[i].to]=dis[now]+e[i].dis;
                    pre[e[i].to]=now;
                    last[e[i].to]=i;
                    flow[e[i].to]=min(flow[now],e[i].flow);//
                    if (!vis[e[i].to]) {
                        vis[e[i].to]=1;
                        q.push(e[i].to);
                    }
                }
            }
        }
        return pre[t]!=-1;
    }

    void MCMF(LL s, LL t) {
        while (spfa(s,t)) {  //+1
            LL now=t;
            zdl+=flow[t];
            fy+=flow[t]*dis[t];
            sum[++tot]=fy;//得到sum[]
            s1[tot]=sum[tot]-sum[tot-1];//得到s1[]

            while (now!=s) {
                //从源点一直回溯到汇点
                e[last[now]].flow-=flow[t];//flow和dis容易搞混
                e[last[now]^1].flow+=flow[t];
                now=pre[now];
            }
        }
    }

}min_Flow;

int a[25][25], b[25][25], c[25][25];
int xx[]={0, 0, 1, -1, 1, 1, -1, -1};
int yy[]={1, -1, 0, 0, -1, 1, -1, 1};
int main() {
    int s1=0, s2=0;
    int s=0, t=1300;
    min_Flow.init(t+5);//初始化用到的最大点数编号

    int n, m; scanf("%d%d", &n, &m);
    for(int i=1; i<=n; i++){
        for(int j=1; j<=m; j++){
            scanf("%1d", &a[i][j]);
            if(a[i][j]==1){
                s1++;
                int l=(i-1)*(m)+j, mid=n*m+l, r=2*n*m+l;
                min_Flow.add(s, mid, 1, 0);
            }
        }
    }
    for(int i=1; i<=n; i++){
        for(int j=1; j<=m; j++){
            scanf("%1d", &b[i][j]);
            if(b[i][j]==1){
                s2++;

                int l=(i-1)*(m)+j, mid=n*m+l, r=2*n*m+l;
                min_Flow.add(mid, t, 1, 0);
            }
        }
    }
    for(int i=1; i<=n; i++){
        for(int j=1; j<=m; j++){
            scanf("%1d", &c[i][j]);
        }
    }
    if(s1!=s2){
        printf("-1\n");
        return 0;
    }

    for(int i=1; i<=n; i++){
        for(int j=1; j<=m; j++){
            int l=(i-1)*(m)+j, mid=n*m+l, r=2*n*m+l;
            /*按初始和结果建图*/
            if(a[i][j]==b[i][j]){
                min_Flow.add(l, mid, c[i][j]/2, 0);
                min_Flow.add(mid, r, c[i][j]/2, 0);
            }
            else{
                if(a[i][j]==1){

                    min_Flow.add(l, mid, c[i][j]/2, 0);
                    min_Flow.add(mid, r, (c[i][j]+1)/2, 0);
                }
                else{
                    min_Flow.add(l, mid, (c[i][j]+1)/2, 0);
                    min_Flow.add(mid, r, (c[i][j])/2, 0);

                }
            }
            /*按连通块建图*/
            for(int k=0; k<8; k++){
                int l=(i-1)*(m)+j, r=2*n*m+l;
                int x=i+xx[k], y=j+yy[k];
                if(x>=1&&x<=n&&y>=1&&y<=m){
                    int l1=(x-1)*(m)+y, r1=2*n*m+l1;
                    min_Flow.add(r, l1, 1<<10, 1);
                    min_Flow.add(r1, l, 1<<10, 1);
                }
            }

        }
    }
    min_Flow.MCMF(s, t);//S-T
    if(min_Flow.zdl==s1){
        printf("%lld\n", min_Flow.fy);//最大流和费用
    }
    else{
        printf("-1\n");
    }

    return 0;
}

posted @   liweihang  阅读(149)  评论(0编辑  收藏  举报
编辑推荐:
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
Live2D
欢迎阅读『[CQOI2012]交换棋子 拆成3点的费用流』
点击右上角即可分享
微信分享提示