【BZOJ2668】交换棋子(CQOI2012)-费用流+拆点

测试地址:交换棋子
做法:本题需要使用费用流+拆点。
容易想到,把全部白色棋子都移动到目标位置的话,剩下的黑色棋子也自然都移动到目标位置了,因此我们只考虑白色棋子。又注意到一枚棋子的移动可以看成一份流量,这样问题就转化成了网络流问题,因此我们要考虑如何将题目中的限制条件表示出来。
注意到,一枚棋子在移动的路径中,交换的次数是它经过的点数 1 ,而对每个点的影响是,对于起点和终点使用了 1 次交换,对于路径中的其他点使用了 2 次交换。我们发现这个很难用传统的拆点办法来搞定,因为点作为起点,终点,起始点时的贡献是不一样的。我们进一步思考,发现这三种情况等价于:仅从该点出,仅进入该点,既进入该点又从该点出。因此我们把一个点拆成三个点,在它们之间顺次连边,如果从第一个点进入第二个点,表示路径进入了该点,如果从第二个点进入第三个点,表示路径从该点出,这样我们就能表现它们的贡献了。考虑三种情况:
1.该点起始状态和终止状态相同,那么该点的第二个点的入度和出度应该相等,所以两条边的容量限制均为 l i m i t 2
2.该点上原来是白子,需要变成黑子,那么该点的第二个点的出度比入度大 1 ,所以两条边的容量应该依次为 l i m i t 2 , l i m i t + 1 2
3.该点上原来是黑子,需要变成白子,那么该点的第二个点的入度比出度大 1 ,所以两条边的容量应该依次为 l i m i t + 1 2 , l i m i t 2
这样我们就把每个点交换次数的限制表现了出来。那么我们再开一个源点和一个汇点,从源点向所有原来是白子的点的第二个点连容量为 1 的边,从所有要变成白子的点的第二个点向汇点连容量为 1 的边,再从每个点的第三个点向与它相邻的点的第一个点连容量为 i n f 的边,这样这个网络的每一种流都对应着一种合法的方案了。
那么怎么求最小的解呢?只需要把第三个点向第一个点连的边附上 1 的费用,做费用流即可。
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
const int inf=1000000000;
int n,m,S,T,first[2010]={0},tot=1,sum1=0,sum2=0;
int dis[2010],last[2010],laste[2010];
char in[21][21],out[21][21],val[21][21];
bool vis[2010]={0};
struct edge
{
    int v,next,f,c;
}e[100010];
queue <int> Q;

int point(int x,int y,int type)
{
    return (x-1)*m+y+type*n*m;
}

void insert(int a,int b,int f,int c)
{
    e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,e[tot].c=c,first[a]=tot;
    e[++tot].v=a,e[tot].next=first[b],e[tot].f=0,e[tot].c=-c,first[b]=tot;
}

void init()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%s",in[i]+1);
    for(int i=1;i<=n;i++)
        scanf("%s",out[i]+1);
    for(int i=1;i<=n;i++)
        scanf("%s",val[i]+1);
    S=3*n*m+1,T=3*n*m+2;

    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            if (in[i][j]-'0') insert(S,point(i,j,1),1,0),sum1++;
            if (out[i][j]-'0') insert(point(i,j,1),T,1,0),sum2++;

            int limit=val[i][j]-'0';
            if (!(in[i][j]-'0')&&out[i][j]-'0') insert(point(i,j,0),point(i,j,1),(limit+1)/2,0);
            else insert(point(i,j,0),point(i,j,1),limit/2,0);
            if (in[i][j]-'0'&&!(out[i][j]-'0')) insert(point(i,j,1),point(i,j,2),(limit+1)/2,0);
            else insert(point(i,j,1),point(i,j,2),limit/2,0);

            for(int ki=-1;ki<=1;ki++)
                for(int kj=-1;kj<=1;kj++)
                {
                    if (ki==0&&kj==0) continue;
                    if (i+ki<1||i+ki>n||j+kj<1||j+kj>m) continue;
                    insert(point(i,j,2),point(i+ki,j+kj,0),inf,1);
                }
        }
}

bool spfa()
{
    for(int i=1;i<=T;i++)
        dis[i]=inf;
    dis[S]=0;
    Q.push(S);
    vis[S]=1;
    while(!Q.empty())
    {
        int v=Q.front();Q.pop();
        for(int i=first[v];i;i=e[i].next)
            if (e[i].f&&dis[v]+e[i].c<dis[e[i].v])
            {
                dis[e[i].v]=dis[v]+e[i].c;
                last[e[i].v]=v;
                laste[e[i].v]=i;
                if (!vis[e[i].v])
                {
                    vis[e[i].v]=1;
                    Q.push(e[i].v);
                }
            }
        vis[v]=0;
    }
    return dis[T]!=inf;
}

void mincost()
{
    if (sum1!=sum2)
    {
        printf("-1");
        return;
    }

    int maxf=0,ans=0;
    while(spfa())
    {
        int x=T,minf=inf;
        while(x!=S)
        {
            minf=min(minf,e[laste[x]].f);
            x=last[x];
        }
        x=T;
        while(x!=S)
        {
            e[laste[x]].f-=minf;
            e[laste[x]^1].f+=minf;
            x=last[x];
        }
        maxf+=minf;
        ans+=minf*dis[T];
    }

    if (maxf<sum1) printf("-1");
    else printf("%d",ans);
}

int main()
{
    init();
    mincost();

    return 0;
}

猜你喜欢

转载自blog.csdn.net/Maxwei_wzj/article/details/80499498
今日推荐