[费用流]luogu P3159 交换棋子

https://www.luogu.org/problemnew/show/P3159

分析

这题更神仙

看到点有出入限制,我们考虑拆点

但既然从棋盘某点入,就要从棋盘某点出

所以一个点被拆成了三个,in,mid,out,其中mid连接源汇

然后从s到初始图的1点连条边,目标图的1点向t连条边,流量均为1

然后再给每个点向其八联通连边,费用为1,流量为1

这时候问题来了,我们发现一个点与源点相连时,它参与交换的次数为1,与汇点相连时同样,但是作为交换的中间点,它们参与交换的次数为2

那我们考虑给流量/2,但是这样问题又出现了,如果它与汇点相连,那么进入的次数要+1,同源点相连时类似

所以我们给流量除时,考虑给原流量加上1再除(当且仅当与汇点相连)

此题如此神仙,实是妙哉!

 

#include <iostream>
#include <cstdio>
#include <queue>
#include <memory.h>
using namespace std;
const int N=21;
const int Inf=2147483647;
struct Pipe {
    int v,c,w,nx;
}g[500*N*N];
int cnt=1,list[3*N*N];
int dis[3*N*N],f[3*N*N];
bool vis[3*N*N];
int n,m,s,t,ans,w,need;
char a[N][N],b[N][N];

void Add(int u,int v,int c,int w) {
    g[++cnt]=(Pipe){v,c,w,list[u]};list[u]=cnt;
    g[++cnt]=(Pipe){u,0,-w,list[v]};list[v]=cnt;
}

bool SPFA() {
    queue<int> q;
    while (!q.empty()) q.pop();
    memset(vis,0,sizeof vis);memset(dis,0x3f,sizeof dis);
    q.push(s);vis[s]=1;dis[s]=0;
    while (!q.empty()) {
        int u=q.front();q.pop();
        for (int i=list[u];i;i=g[i].nx)
            if (g[i].c&&dis[g[i].v]>dis[u]+g[i].w) {
                dis[g[i].v]=dis[u]+g[i].w;f[g[i].v]=i;
                if (!vis[g[i].v]) q.push(g[i].v);
                vis[g[i].v]=1;
            }
        vis[u]=0;
    }
    return dis[t]!=0x3f3f3f3f;
}

void MCF() {
    int x=t,mf=Inf;
    while (f[x]) {
        mf=min(mf,g[f[x]].c);
        x=g[f[x]^1].v;
    }
    x=t;ans+=dis[t];
    while (f[x]) {
        g[f[x]].c-=mf;g[f[x]^1].c+=mf;
        x=g[f[x]^1].v;
    }
}

void Dinic() {
    while (SPFA()) MCF();
}


int main() {
    scanf("%d%d",&n,&m);s=0;t=3*n*m+1;
    for (int i=1;i<=n;i++)
        for (int j=1;j<=m;j++) 
            do {
                scanf("%c",&a[i][j]);
            }
            while (a[i][j]!='0'&&a[i][j]!='1');
    for (int i=1;i<=n;i++)
        for (int j=1;j<=m;j++) {
            do {
                scanf("%c",&b[i][j]);
            }
            while (b[i][j]!='0'&&b[i][j]!='1');
            a[i][j]-=48;b[i][j]-=48;
            if (a[i][j]) Add(s,2*n*m+(i-1)*m+j,1,0),w++;
            if (b[i][j]) Add(2*n*m+(i-1)*m+j,t,1,0),need++;
        }
    if (w!=need) {
        printf("-1");
        return 0;
    }
    need=0;
    for (int i=1;i<=n;i++)
        for (int j=1;j<=m;j++) {
            char c;
            do {
                scanf("%c",&c);
            }
            while ('0'>c||c>'9');
            c-=48;
            if (c) {
                Add((i-1)*m+j,2*n*m+(i-1)*m+j,(c+(b[i][j]>a[i][j]))/2,0);
                Add(2*n*m+(i-1)*m+j,n*m+(i-1)*m+j,(c+(a[i][j]>b[i][j]))/2,0);
            }
        }
    for (int i=1;i<=n;i++)
        for (int j=1;j<=m;j++) {
            if (i>1) {
                Add(n*m+(i-2)*m+j,(i-1)*m+j,Inf,1);
                if (j>1) Add(n*m+(i-2)*m+j-1,(i-1)*m+j,Inf,1);
                if (j<m) Add(n*m+(i-2)*m+j+1,(i-1)*m+j,Inf,1);
            }
            if (i<n) {
                Add(n*m+i*m+j,(i-1)*m+j,Inf,1);
                if (j>1) Add(n*m+i*m+j-1,(i-1)*m+j,Inf,1);
                if (j<m) Add(n*m+i*m+j+1,(i-1)*m+j,Inf,1);
            }
            if (j>1) Add(n*m+(i-1)*m+j-1,(i-1)*m+j,Inf,1);
            if (j<m) Add(n*m+(i-1)*m+j+1,(i-1)*m+j,Inf,1);
        }
    Dinic();
    for (int i=list[t];i;i=g[i].nx)
        if (!g[i].c) {
            printf("-1");
            return 0;
        }
    printf("%d",ans);
}
View Code

 

posted @ 2019-07-08 07:41  Vagari  阅读(153)  评论(0编辑  收藏  举报