【费用流】BZOJ2668 [CQOI2012]交换棋子

【题目】
原题地址
去看题吧。

【题目分析】
表示第一眼看到这个题,没有任何感觉,只会爆搜。
冷静分(mo)析(yu)了一下发现,我们可以思考一下网络流什么的,然而并不会建图2333.

【解题思路】
这题的建图十分巧妙。

我们只需要把黑色棋子移动到目标位置即可,那么考虑一条合法的移动路径,开始和结尾的点只交换了一次,中间的点交换了两次.
所以我们可以把每个点拆成三个点 ( a , b , c ) .
如果这个点是原图中的黑点.
a b 连流量为 c [ i ] [ j ] 2 ,费用为0的边.
b c 连流量为 ( c [ i ] [ j ] + 1 ) 2 ,费用为0的边.
S b 连流量为1,费用为0的边.

如果这个点是新图中的黑点.
a b 连流量为 c [ i ] [ j ] + 1 2 ,费用为0的边.
b c 连流量为 c [ i ] [ j ] 2 ,费用为0的边.
b T 连流量为1,费用为0的边.

如果这个点在新图和原图中都是白点.
a b 连流量为 c [ i ] [ j ] 2 ,费用为0的边.
b c 连流量为 c [ i ] [ j ] 2 ,费用为0的边.
对于相邻的两个点 ( x , y )
x . c y . a 连流量为 i n f ,费用为1的边.
跑最小费用最大流即可.

【参考代码】

#include<bits/stdc++.h>
#define id(x,y) ((x-1)*m+y)
using namespace std;

const int INF=707406378;
const int N=25;
const int M=2e6+10;
int S,T,b1,b2;
int n,m,tot,sum,flow,ans;
int head[M],dis[M],from[M];
int num[N][N],a[N][N],b[N][N],c[N][N];
char s1[N][N],s2[N][N],s3[N][N];
bool inq[M];
queue<int>q;

struct Tway
{
    int u,v,nex,w,c;
};
Tway e[M];

void add(int u,int v,int w,int c)
{
    e[++tot]=(Tway){u,v,head[u],w,c};head[u]=tot;
    e[++tot]=(Tway){v,u,head[v],0,-c};head[v]=tot;
}

void init()
{
    scanf("%d%d",&n,&m);sum=n*m;S=0;T=sum*3+1;tot=1;
    for(int i=1;i<=n;++i)
        scanf("%s",s1[i]+1);
    for(int i=1;i<=n;++i)
        scanf("%s",s2[i]+1);
    for(int i=1;i<=n;++i)
        scanf("%s",s3[i]+1);
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
        {
            a[i][j]=s1[i][j]-'0';
            if(a[i][j])
                ++b1;
            b[i][j]=s2[i][j]-'0';
            if(b[i][j])
                ++b2;
            c[i][j]=s3[i][j]-'0';
            if(a[i][j] && b[i][j])
                a[i][j]=b[i][j]=0,--b1,--b2;
        }
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
        {
            int now=id(i,j);num[i][j]=now;
            if(a[i][j])
            {
                add(S,now,1,0);add(sum+now,now,c[i][j]/2,0);
                add(now,2*sum+now,(c[i][j]+1)/2,0);
            }
            else
            if(b[i][j])
            {
                add(now,T,1,0);add(sum+now,now,(c[i][j]+1)/2,0);
                add(now,2*sum+now,c[i][j]/2,0);
            }
            else
            {
                add(sum+now,now,c[i][j]/2,0);
                add(now,2*sum+now,c[i][j]/2,0);
            }
        }
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
        {
            if(i>1) add(2*sum+num[i][j],sum+num[i-1][j],INF,1);
            if(j>1) add(2*sum+num[i][j],sum+num[i][j-1],INF,1);
            if(i<n) add(2*sum+num[i][j],sum+num[i+1][j],INF,1);
            if(j<m) add(2*sum+num[i][j],sum+num[i][j+1],INF,1);
            if(i>1 && j>1)  add(2*sum+num[i][j],sum+num[i-1][j-1],INF,1);
            if(i<n && j<m)  add(2*sum+num[i][j],sum+num[i+1][j+1],INF,1);
            if(i>1 && j<m)  add(2*sum+num[i][j],sum+num[i-1][j+1],INF,1);
            if(i<n && j>1)  add(2*sum+num[i][j],sum+num[i+1][j-1],INF,1);
        }       
}

bool spfa()
{
    memset(dis,127/3,sizeof(dis));
    q.push(S);inq[S]=1;dis[S]=0;
    while(!q.empty())
    {
        int u=q.front();q.pop();inq[u]=0;
        for(int i=head[u];i;i=e[i].nex)
        {
            int v=e[i].v;
            if(e[i].w && dis[u]+e[i].c<dis[v])
            {
                dis[v]=dis[u]+e[i].c;from[v]=i;
                if(!inq[v])
                {
                    q.push(v);
                    inq[v]=1;
                }
            }
        }
    }
    return dis[T]!=INF;
}

void solve()
{
    if(b1==b2)
    {
        while(spfa())
        {
            int f=INF;
            for(int i=from[T];i;i=from[e[i].u])
                f=min(f,e[i].w);
            for(int i=from[T];i;i=from[e[i].u])
                e[i].w-=f,e[i^1].w+=f;
            flow+=f;ans+=f*dis[T];
        }
        if(flow!=b1)
            ans=-1;
    }
    else
        ans=-1;
    printf("%d\n",ans);
}

int main()
{
    freopen("BZOJ2668.in","r",stdin);
    freopen("BZOJ2668.out","w",stdout);

    init();
    solve();

    return 0;
}

【总结】
这种数据范围很小的题目,如果搜索和状压之类的东西做不了,可以朝网络流方面考虑。

猜你喜欢

转载自blog.csdn.net/dream_lolita/article/details/79979244
今日推荐