[LOJ2321][清华集训2017]无限之环-最小费用最大流

无限之环

题目描述

曾经有一款流行的游戏,叫做 Infinity Loop,先来简单的介绍一下这个游戏:

游戏在一个 n×m 的网格状棋盘上进行,其中有些小方格中会有水管,水管可能在方格某些方向的边界的中点有接口,所有水管的粗细都相同,所以如果两个相邻方格的公共边界的中点都有接头,那么可以看作这两个接头互相连接。水管有以下 15 种形状:

img1
img2

游戏开始时,棋盘中水管可能存在漏水的地方。

形式化地:如果存在某个接头,没有和其它接头相连接,那么它就是一个漏水的地方。

玩家可以进行一种操作:选定一个含有非直线型水管的方格,将其中的水管绕方格中心顺时针或逆时针旋转 90 度。

直线型水管是指左图里中间一行的两种水管。

现给出一个初始局面,请问最少进行多少次操作可以使棋盘上不存在漏水的地方。

输入格式

第一行两个正整数 n,m ,代表网格的大小。

接下来 n 行每行 m 个数,每个数是 [0,15] 中的一个,你可以将其看作一个 4 位的二进制数,从低到高每一位分别代表初始局面中这个格子上、右、下、左方向上是否有 水管接头。

特别地,如果这个数是 0,则意味着这个位置没有水管。

比如 3(0011(2)) 代表上和右有接头,也就是一个 L 型,而 12(1100(2)) 代表下和左有接头,也就是将 L 型旋转 180 度。

输出格式

输出共一行,表示最少操作次数。如果无法达成目标,输出 1 .

样例输入 1

2 3
3 14 12
3 11 12

样例输出 1

2

样例输入 2

3 2
1 8
5 10
2 4

样例输出 2

-1

样例输入 3

3 3
9 11 3
13 15 7
12 14 6

样例输出 3

16

数据范围与提示

n×m2000


这建图差评……
建图部分代码又臭又长……
(虽然不难调)


思路:
满足联通的情况下费用最小,那么就是最小费用最大流了~

考虑把每个节点拆成4个节点,对应四方向上的接口。
对原图黑白染色,令其中一种颜色的接口节点连向源,另一种的接口连向汇,同时旋转暂时采用某种神奇的方式描述。
此时,对于连向源的所有颜色的点,统计接口个数,若最后最大流的结果为接口个数/2,那么存在合法方案,则最小费用即为答案。

于是就是考虑如何用神奇的方式描述旋转了。

对于下面的内容,所有的点编号如下:

   A

D  O  B

   C

所有的边使用 起点->终点 (流量,费用) 描述,所有旋转默认为顺时针。

考虑本质不同的水管其实只有 5 <script type="math/tex" id="MathJax-Element-555">5</script>种,那么分情况讨论,依次描述所有的旋转:

1.形如

   A
   |
D  O  B

   C

即只有一个接口的情况,考虑这样连边:
A->B (1,1) 对应转90度
A->C (1,2) 对应转180度
A->D (1,1) 对应转270度

2.形如:

   A
   |
D  O--B

   C

即有两个接口且不为一条直线的情况:
A->C (1,1)
B->D (1,1)

转90度或270度时,考虑使用A->C或B->D边分别描述。
转180度时,相当于同时使用了A-C和B->D边。

3.形如

   A
   |
D  O--B
   |
   C

即有三个接口的情况:
A->D (1,1) 对应旋转270度
C->D (1,1) 对应旋转90度
B->D (1,2) 对应旋转180度

4、5.剩下两种

   A         A
   |         |
D--O--B   D  O  B
   |         |
   C         C

一个转了没用,另一个不能转,无视。

然后建图直接跑费用流即可~

#include<bits/stdc++.h>

using namespace std;

inline int read()
{
    int x=0;char ch=getchar();
    while(ch<'0' || '9'<ch)ch=getchar();
    while('0'<=ch && ch<='9')x=x*10+(ch^48),ch=getchar();
    return x;
}

const int N=8009;
const int M=200009;
const int P=2009;
const int Inf=2139062143;

int n,m;

inline bool chkmin(int &a,int b){if(a>b){a=b;return 1;}return 0;}

namespace mcmf
{
    int to[M],nxt[M],beg[N],w[M],cost[M],tot=1;
    int s,t,dis[N],fa[N],flow;
    bool inq[N];
    queue<int> q;

    inline void adde(int u,int v,int f,int c)
    {
        to[++tot]=v;
        nxt[tot]=beg[u];
        w[tot]=f;
        cost[tot]=c;
        beg[u]=tot;
    }

    inline void add(int u,int v,int f,int c,int ty=1)
    {
        if(u==1e9)
        {
            if(ty)u=s;
            else u=v,v=t;
        }
        else if(!ty)
            swap(u,v);
        adde(u,v,f,c);adde(v,u,0,-c);
    }

    inline bool spfa()
    {
        memset(dis,127,sizeof(dis));
        q.push(s);dis[s]=0;
        while(!q.empty())
        {
            int u=q.front();q.pop();
            inq[u]=0;
            for(int i=beg[u],v;i;i=nxt[i])
                if(w[i]>0 && chkmin(dis[v=to[i]],dis[u]+cost[i]))
                {
                    fa[v]=i;
                    if(!inq[v])
                        inq[v]=1,q.push(v);
                }
        }
        return dis[t]!=Inf;
    }

    inline int mcmf()
    {
        int mn=Inf;
        for(int i=t;i!=s;i=to[fa[i]^1])
            chkmin(mn,w[fa[i]]);
        for(int i=t;i!=s;i=to[fa[i]^1])
            w[fa[i]]-=mn,w[fa[i]^1]+=mn;
        flow+=mn;
        return dis[t]*mn;
    }

    inline int run()
    {
        int ret=0;
        while(spfa())
            ret+=mcmf();
        return ret;
    }
}

using namespace mcmf;

int pop[29],di[9];
int g[P][P],pos,sum;
int id[P][P][4];
int dx[]={-1,0,1,0};
int dy[]={0,1,0,-1};

inline int lowbit(int x){return x&(-x);}

inline void init(int x,int y,int ty)
{
    int col=(x+y)&1;
    if(pop[ty]==1)
    {
        add(1e9,id[x][y][di[ty]],1,0,col);
        add(id[x][y][di[ty]],id[x][y][(di[ty]+1)%4],1,1,col);
        add(id[x][y][di[ty]],id[x][y][(di[ty]+2)%4],1,2,col);
        add(id[x][y][di[ty]],id[x][y][(di[ty]+3)%4],1,1,col);
    }
    else if(pop[ty]==2)
    {
        int in1=di[lowbit(ty)],in2=di[ty-lowbit(ty)];
        add(1e9,id[x][y][in1],1,0,col);
        add(1e9,id[x][y][in2],1,0,col);
        if(in1==(in2+2)%4)return;
        if(in1>in2)swap(in1,in2);
        if(in1==0 && in2==3)in1=3;
        add(id[x][y][in1],id[x][y][(in1+2)%4],1,1,col);
        add(id[x][y][(in1+1)%4],id[x][y][(in1+3)%4],1,1,col);
    }
    else if(pop[ty]==3)
    {
        int out1=di[15-ty];
        for(int i=0;i<=3;i++)
            if(i!=out1)
                add(1e9,id[x][y][i],1,0,col);
        add(id[x][y][(out1+1)%4],id[x][y][out1],1,1,col);
        add(id[x][y][(out1+2)%4],id[x][y][out1],1,2,col);
        add(id[x][y][(out1+3)%4],id[x][y][out1],1,1,col);
    }
    else if(pop[ty]==4)
        for(int i=0;i<=3;i++)
            add(1e9,id[x][y][i],1,0,col);
}

inline bool in(int x,int y)
{
    return 1<=x && x<=n && 1<=y && y<=m;
}

int main()
{
    n=read();m=read();
    s=++pos;t=++pos;
    for(int i=1;i<19;i++)
        pop[i]=pop[i>>1]+(i&1);
    di[1]=0;di[2]=1;
    di[4]=2;di[8]=3;

    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            g[i][j]=read();
            for(int k=0;k<=3;k++)
                id[i][j][k]=++pos;
            init(i,j,g[i][j]);
            sum+=pop[g[i][j]];
        }

    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            if((i+j)&1)
                for(int k=0;k<=3;k++)
                {
                    int x=i+dx[k],y=j+dy[k];
                    if(!in(x,y))continue;
                    add(id[i][j][k],id[x][y][(k+2)%4],1,0);
                }

    int ret=run();
    if(sum/2!=flow)
        return puts("-1"),0;
    else
        printf("%d\n",ret);

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值