【清华集训】2017 无限之环

无限之环


题目

链接

洛谷

描述

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

游戏在一个 n ∗ m 的网格状棋盘上进行,其中有些小方格中会有水管,水管可能在

格某些方向的边界的中点有接口,所有水管的粗细都相同,所以如果两个相邻方格的

共边界的中点都有接头,那么可以看作这两个接头互相连接。水管有以下 15 种形状:

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

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

地方。

玩家可以进行一种操作:选定一个含有非直线型水管的方格,将其中的水管绕方格

中心顺时针或逆时针旋转 90 度。

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

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

输入格式

从文件 infinityloop.in 中读入数据。

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

接下来 n 行每行 m 个数,每个数是 [0,15] 中的一个,你可以将其看作一个 4 位的

二进制数,从低到高每一位分别代表初始局面中这个格子上、右、下、左方向上是否有

水管接头。

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

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

有接头,也就是将 L 型旋转 180 度。

输出格式

输出到文件 infinityloop.out 中。

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

子任务

解题法

评价:神奇的网络流神题

出这种题的dalao,%%%

Round 1:黑白染色

对于一个方格,黑白染色后,就分成了两个部分,并且保证同色之间不连边。对于一个格子,需要连接的就是上下左右,全部与原方格异色。

Round 2:方格->网络流的边

拆点,建图。
这道题的精髓就体现在建图。
(未经说明的边均与S或T相连)

单头型

四条边分别是:
- 向上,容量1,费用0。
- 向左右,容量1,费用1。
- 向下,容量1,费用2。

直线型(不能动)

以上下(编号5)为例,建的边有:

  • 向上下,容量1,费用0。

弯弯1

要命的就是转来转去怎么办?

顺时针转90°其实相当于把上面那根管子搞到下面来,费用为1。
转180°同理,两根一起调头,费用为2。
于是我们就解决了这么一个问题。

建的边有:
- 向上,容量1,费用0。
- 向右,容量1,费用0。
- 向下与向上相连,容量1,费用1。
- 向左与向右相连,容量1,费用1。

应为容量只有1,所以想要跑出转180°的那种情况费用必须为2.。
(完美!)

弯弯2

以我们的经验,可以秒掉其中三条边:
- 向左右上,容量1,费用0。
仔细一想,发现空的那个地方其实可以由三条边转移而来,
再结合弯弯2型的另外三个图,其实很容易发现剩余边的建法:
- 向左与向下相连,容量1,费用1。
- 向右与向下相连,容量1,费用1。
- 向上与向下相连,容量1,费用2。

十字型

最无脑的。
- 向上下左右,容量1,费用0.。

结束!

至此,我们就完成了建边过程。

至于细枝末节的问题,不再讨论(这不本质)

Round 3:网络流乱跑!

费用流即可。

代码:

#include<bits/stdc++.h>

#define FN "infinityloop"

const int MN=2000+5;
const int N=8000+5;
const int INF=1e9+7;

namespace LoLiK {

    struct Edge {
        int v,nxt,cot,fow;
    }e[20*N];

    int S,T,n,m,id[MN][MN][4];
    int h[N],tot=1,cnt=0;
    int dx[4]={-1,0,1,0};
    int dy[4]={0,1,0,-1};
    //w: 容量 f: 费用
    void add_edge(int u,int v,int flow,int cost) {
        tot++;
        e[tot].v=v;
        e[tot].fow=flow;
        e[tot].cot=cost;
        e[tot].nxt=h[u];
        h[u]=tot;
        tot++;
        e[tot].v=u;
        e[tot].fow=0;
        e[tot].cot=-cost;
        e[tot].nxt=h[v];
        h[v]=tot;
    }

    void add(int u,int v,int flow,int cost,bool col) {
        if(col)
            add_edge(u,v,flow,cost);
        else if(u==S)
            add_edge(v,T,flow,cost);
        else
            add_edge(v,u,flow,cost);
    }

    void work(int i,int j,int opt,int pos,bool col) {
                            //种类,方向
        switch(opt) {
            case 1:add(S,id[i][j][pos],1,0,col);
                   add(id[i][j][pos],id[i][j][(pos+1)%4],1,1,col);
                   add(id[i][j][pos],id[i][j][(pos+2)%4],1,2,col);
                   add(id[i][j][pos],id[i][j][(pos+3)%4],1,1,col);
                   break;
            case 2:add(S,id[i][j][pos],1,0,col);
                   add(S,id[i][j][(pos+1)%4],1,0,col);
                   add(id[i][j][pos],id[i][j][(pos+2)%4],1,1,col);
                   add(id[i][j][(pos+1)%4],id[i][j][(pos+3)%4],1,1,col);
                   break;
            case 3:add(S,id[i][j][pos],1,0,col);
                   add(S,id[i][j][(pos+1)%4],1,0,col);
                   add(S,id[i][j][(pos+2)%4],1,0,col);
                   add(id[i][j][pos],id[i][j][(pos+3)%4],1,1,col);
                   add(id[i][j][(pos+1)%4],id[i][j][(pos+3)%4],1,2,col);
                   add(id[i][j][(pos+2)%4],id[i][j][(pos+3)%4],1,1,col);
                   break;
            case 4:add(S,id[i][j][pos],1,0,col);
                   add(S,id[i][j][(pos+2)%4],1,0,col);
                   break;
            case 5:add(S,id[i][j][pos],1,0,col);
                   add(S,id[i][j][(pos+1)%4],1,0,col);
                   add(S,id[i][j][(pos+2)%4],1,0,col);
                   add(S,id[i][j][(pos+3)%4],1,0,col);
                   break;
            default:break;
        }
    }

    bool vis[N];
    int dis[N],pre[N],prv[N];
    int q[N*N];

    bool SPFA() {
        for(int i=S;i<=T;i++) {
            vis[i]=false;
            dis[i]=INF;
        }
        int head=0,tail=0;
        q[++tail]=S;
        vis[S]=true;
        dis[S]=0;
        while(head<tail) {
            int u=q[++head];
            vis[u]=false;
            for(int i=h[u];i;i=e[i].nxt) {
                int v=e[i].v;
                if(e[i].fow && dis[v]>dis[u]+e[i].cot) {
                    dis[v]=dis[u]+e[i].cot;
                    pre[v]=u;
                    prv[v]=i;
                    if(!vis[v]) {
                        q[++tail]=v;
                        vis[v]=true;
                    }
                }
            }
        }
        return (dis[T]!=INF);
    }

    void caozuo() {
        scanf("%d%d",&n,&m);
        S=0;
        T=n*m*4+1;
        int sum=0;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++) {
                int x;
                bool col;
                scanf("%d",&x);
                col=(i+j)&1;
                for(int k=0;k<4;k++)
                    id[i][j][k]=++cnt;
                switch(x) {
                    case 1:work(i,j,1,0,col);
                           sum++;
                           break;
                    case 2:work(i,j,1,1,col);
                           sum++;
                           break;
                    case 3:work(i,j,2,0,col);
                           sum+=2;
                           break;
                    case 4:work(i,j,1,2,col);
                           sum++;
                           break;
                    case 5:work(i,j,4,0,col);
                           sum+=2;
                           break;
                    case 6:work(i,j,2,1,col);
                           sum+=2;
                           break;
                    case 7:work(i,j,3,0,col);
                           sum+=3;
                           break;
                    case 8:work(i,j,1,3,col);
                           sum++;
                           break;
                    case 9:work(i,j,2,3,col);
                           sum+=2;
                           break;
                    case 10:work(i,j,4,1,col);
                            sum+=2;
                            break;
                    case 11:work(i,j,3,3,col);
                            sum+=3;
                            break;
                    case 12:work(i,j,2,2,col);
                            sum+=2;
                            break;
                    case 13:work(i,j,3,2,col);
                            sum+=3;
                            break;
                    case 14:work(i,j,3,1,col);
                            sum+=3;
                            break;
                    case 15:work(i,j,5,0,col);
                            sum+=4;
                            break;
                    default:break;
                }
            }
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
                if((i+j)&1)
                    for(int k=0;k<4;k++) {
                        int x=i+dx[k],y=j+dy[k];
                        if(x>=1 && x<=n && y>=1 && y<=m)
                            add_edge(id[i][j][k],id[x][y][(k+2)%4],1,0);
                    }
        int Flow=0,Cost=0;
        while(SPFA()) {
            int delta=INF;
            for(int i=T;i!=S;i=pre[i])
                delta=std::min(delta,e[prv[i]].fow);
            Cost+=delta*dis[T];
            Flow+=delta;
            for(int i=T;i!=S;i=pre[i]) {
                e[prv[i]].fow-=delta;
                e[prv[i]^1].fow+=delta;
            }
        }
        if(Flow!=sum/2) {
            printf("-1\n");
            return ;
        }
        printf("%d\n",Cost);
        return ;
    }
}

int main() {
    LoLiK::caozuo();
    return 0;
}

请注意:

对上下左右的编号很重要!
注意判断不可行的情况!

总结(讲垃圾话)

趁着放假在家做完了这道题,用了大半天,还是有很多感想。
把思路落在笔尖上。
怎么编号,怎么建图,不能只说,写写画画也很重要。

猜你喜欢

转载自blog.csdn.net/LoLiK1314/article/details/81570472