码迷,mamicode.com
首页 > 其他好文 > 详细

[CQOI2012]交换棋子(最小费用最大流)

时间:2020-03-04 10:00:53      阅读:72      评论:0      收藏:0      [点我收藏+]

标签:nbsp   false   i++   sans   out   void   min   问题   包含   

[CQOI2012]交换棋子(luogu)

 

Description

 

题目描述

 

有一个n行m列的黑白棋盘,你每次可以交换两个相邻格子(相邻是指有公共边或公共顶点)中的棋子,

最终达到目标状态。要求第i行第j列的格子只能参与mi,j次交换。

 

输入格式

 

第一行包含两个整数n,m(1<=n, m<=20)。以下n行为初始状态,每行为一个包含m个字符的01串,

其中0表示黑色棋子,1表示白色棋子。以下n行为目标状态,格式同初始状态。

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

 

输出格式

 

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

 

 

Soltion

先判初始状态和目标状态黑白棋子数是否相等,若不相等直接输出-1

忽略白色棋子,将问题看做把黑色棋子移动到目标位置

容易想到网络流,从 起点 向 每个初始状态为黑色棋子的位置(后文mid) 连一条流量为 1,费用为0 的边

从 每个目标状态为黑色棋子的位置(后文mid) 向 终点 连一条流量为 1 ,费用为0的边

问题在于对交换次数的限制,且一次交换两个位置都算多了一次交换

我们将一个位置的交换分为黑色点交换进去和交换出去两种情况

发现如果某个位置的初始状态和目标状态相同,那这个位置交换进去和交换出去的次数相等

如果不相同,

若初始状态为黑色,则交换出去的次数比交换进去多1;

若目标状态为黑色,则交换进去的次数比交换出去多1;

于是可以想到将一个位置分成3个网络流中的点,分别代表in,mid,out

根据上述分析将交换次数合法分给in-mid边和mid-out边的流量

若位置 i 可与位置 j 交换,则从out_i向in_j连一条流量为inf,费用为1的边

Code

#include <cstdio>
#include <cstdlib>
#include <queue>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=12e2+10,M=1e6;
int head[N],nxt[M],ver[M],cost[M],edge[M],tot=1;
int s,t,n,m,a[21][21],incf[N],dis[N],maxflow,mincost,pre[N];
char be[21][21],st[21][21],ch[21][21];
int py[8][2]={{1,0},{0,1},{-1,0},{0,-1},{1,1},{1,-1},{-1,1},{-1,-1}};
int id(int x,int y,int inv)
{
    return (x-1)*m+y+inv*n*m;
}
void add(int u,int v,int w,int c)
{
    ver[++tot]=v,nxt[tot]=head[u],edge[tot]=w,cost[tot]=c,head[u]=tot;
    ver[++tot]=u,nxt[tot]=head[v],edge[tot]=0,cost[tot]=-c,head[v]=tot;
}
bool in[N];
bool spfa()
{
    memset(dis,0x3f,sizeof(dis));
    memset(pre,0,sizeof(pre));
    int last=dis[s];
    queue <int> q;
    dis[s]=0,q.push(s),incf[s]=1<<30;
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        in[x]=false;
        for(int i=head[x],y;i;i=nxt[i])
            if(edge[i]>0 && dis[y=ver[i]]>dis[x]+cost[i])
            {
                dis[y]=dis[x]+cost[i];
                incf[y]=min(incf[x],edge[i]);
                pre[y]=i;
                if(!in[y]) in[y]=true,q.push(y);
            }
    }
    return dis[t]!=last;
}
void update()
{
    maxflow+=incf[t],mincost+=incf[t]*dis[t];
    int x=t;
    while(x!=s)
    {
        int i=pre[x];
        edge[i]-=incf[t],edge[i^1]+=incf[t];
        x=ver[i^1];
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%s",be[i]+1);
    for(int i=1;i<=n;i++)
        scanf("%s",st[i]+1);
    for(int i=1;i<=n;i++)
    {
        scanf("%s",ch[i]+1);
        for(int j=1;j<=m;j++)
            a[i][j]=ch[i][j]-0;
    }
    s=0,t=n*m*3+1;
    int ans1=0,ans2=0;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            for(int k=0;k<8;k++)
            {
                int ni=i+py[k][0],nj=j+py[k][1];
                if(ni<1 || ni>n || nj<1 || nj>m) continue;
                add(id(i,j,2),id(ni,nj,0),1<<30,0);
            }
            if(be[i][j]==0) ans1++,add(s,id(i,j,1),1,0);
            if(st[i][j]==0) ans2++,add(id(i,j,1),t,1,0);
            if(a[i][j]==0) continue;
            if(a[i][j]%2==0 || be[i][j]==st[i][j])
            {
                add(id(i,j,0),id(i,j,1),a[i][j]/2,1);
                add(id(i,j,1),id(i,j,2),a[i][j]/2,1);
                continue;    
            }
            if(be[i][j]==0)
            {
                add(id(i,j,0),id(i,j,1),a[i][j]/2,1);
                add(id(i,j,1),id(i,j,2),(a[i][j]+1)/2,1);
            }
            else
            {
                add(id(i,j,0),id(i,j,1),(a[i][j]+1)/2,1);
                add(id(i,j,1),id(i,j,2),a[i][j]/2,1);
            }
        }
    if(ans1!=ans2)
    {
        puts("-1");
        return 0;
    }
    while(spfa()) update();
    if(maxflow<ans2) puts("-1");
    else printf("%d\n",mincost/2);
    return 0;
}

 

[CQOI2012]交换棋子(最小费用最大流)

标签:nbsp   false   i++   sans   out   void   min   问题   包含   

原文地址:https://www.cnblogs.com/hsez-cyx/p/12407541.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!