洛谷P4003 无限之环(infinityloop)(网络流,费用流)

洛谷题目传送门

题目

题目描述

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

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

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

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

玩家可以进行一种操作:选定一个含有非直线型水管的方格,将其中的水管绕方格中心顺时针或逆时针旋转 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

说明

【样例 1 解释】

样例 1 棋盘如下

旋转方法很显然,先将左上角虚线方格内的水管顺时针转 90 度

然后右下角虚线方格内的水管逆时针旋转 90 度,这样就使得水管封闭了

【样例 2 解释】

样例 2 为题目描述中的第一张图片,无法达成目标。

【样例 3 解释】

样例 3 为题目描述中的第二张图片,将除了中心方格以外的每个方格内的水管都转 180 度即可。

思路分析

表示这是一道思维神题。。。。。。

有人第一眼看上去觉得这要跑费用流吗?

然而只要会建图,剩下的就是套模板的事了。

我们这样来理解。对于每个方格上的水管的每一个支管,有且仅有一个其它方格上的支管与其相连,这样就不会漏水了。用网络流知识表述,就是每个支管容量只能为1,且全都要满流,于是跑最小费用可行流

然而即使产生了最优情况,整个管网也不一定是一整个联通块,而可能被分成若干块。因此,怎样强制使每两个相邻的方格上都产生流量呢?就要把源汇点连到每个格子上。而且,还要对每个格点染色,相邻的两个格点,一个连源点,一个连汇点。具体的实现,就要利用格点行列坐标和的奇偶性来判断。

而产生的费用呢?当然是旋转造成的啦!真正的思维就体现在这里了。因为旋转还会造成接触点的变化,所以肯定是要拆点的,一个方格拆成五个点,上下左右中。。。。。。中间点连上源/汇点,并根据支管情况向四周连容量1,费用0的边。四周视作接触点,与对应相邻的另一个接触点连容量1,费用0的边。讨论相邻两个方格格因旋转而产生的有费用的连边,实在是太难了。。。。。。猛然发现,所有的情况,其实只需要在内部进行转化就好了。

所有的方格,我们大致分成以下几类进行讨论。

第一种:射线型

这种好办。射线指向上面,那么就让左、下、右接触点直接连接上接触点。左,右连上去,表示只要转90度,所以费用为1。下面连上去费用为2

第二种:直角型

这种理解起来就有难度了。如果顺时针转90度,会变成这样

相当于原来连上接触点的支管连到了下面,那么上与下建一条容量为1,费用为1的边。同样的道理,逆时针转90度,左与右建一条容量为1,费用为1的边。再来讨论转180度,这时候,会通过已有的边由左、下直接转移到右、上,费用加起来正好是2,所以不用连更多边了。

第三种:T字型

像前面一样讨论,也可以建边。从下向左、右各建一条容量为1,费用为1的边,向上建一条费用为2的边。这里就留给读者自己思考啦。



以上三种情况,每一种都有4个形状,但连边方法都是一样的。

还有直线型,十字型和空的,要么不能转,要么转了没意义,就不用内部建边了。

下面贴代码

#include<cstdio>
#include<cstdlib>
#include<cstring>
using namespace std;
#define R register int
#define UP(U) U+turn*sum
#define RI(U) U+((turn+1)&3)*sum
#define DO(U) U+((turn+2)&3)*sum
#define LE(U) U+((turn+3)&3)*sum
#define MD(U) U+(sum<<2)//上面几个用来计算对应点的数组下标,上下左右中。。。
const int INF=2147483647,N=20009,M=200009;
int sum,P=1,S=0,T;//sum方格总数,P建图循环变量,S、T为源汇点
int he[N],ne[M],to[M],f[M],c[M];//f流量,c费用
int q[N],d[N],pre[N];//q队列,d距离,pre记录最短路
bool inq[N];//标记是否在队列中
inline void in(R&z)//快读
{
    register char c=getchar();
    while(c<‘-‘)c=getchar();
    z=c&15;c=getchar();
    while(c>‘-‘)z*=10,z+=c&15,c=getchar();
}
inline void add(R u,R v,R flow,R cost,R tp)//建边,tp表示染色属性
{
    if(tp){tp=u;u=v;v=tp;}//如果是奇数点,所有的边都要反向,要流出去
    to[++P]=v;ne[P]=he[u];he[u]=P;c[P]=cost;f[P]=flow;
    to[++P]=u;ne[P]=he[v];he[v]=P;c[P]=-cost;
}
#define PB(X) q[t]=X;if(++t==N)t=0
#define PF(X) if(--h<0)h=N-1;q[h]=v//手打了一下双向循环队列
inline bool spfa()//模板,加了两种优化
{
    R h=0,t=1,i,u,v,dn,cnt=1,sum=0;
    for(i=S+1;i<=T;++i)d[i]=INF;
    q[0]=S;inq[0]=1;
    while(h!=t)
    {
        u=q[h];
        if(++h==N)h=0;
        if(d[u]*cnt>sum){PB(u);continue;}//LLL优化
        --cnt;sum-=d[u];
        for(i=he[u];i;i=ne[i])
            if(f[i]&&d[v=to[i]]>(dn=d[u]+c[i]))
            {
                if(inq[v])sum-=d[v];
                else
                {
                    inq[v]=1;++cnt;
                    if(d[v]<d[q[h]]){PB(v);}
                    else{PF(v);}//SLF优化
                }
                pre[v]=i;
                sum+=(d[v]=dn);
            }
        inq[u]=0;
    }
    return d[T]!=INF;
}
int main()
{
    R n,m,i,j,k=1,t,shape,turn,totf=0,mf=0,mc=0;//totf总流量,mf最大可行流,mc最小总费用
    in(n);in(m);
    sum=n*m;T=sum*5+1;
    for(i=0;i<n;++i)
        for(j=0;j<m;++j,++k)
        {
            turn=0;//turn下面会用来翻转,将同类型的水管归类到一起
            t=(i+j)&1;//t是染色属性,只要判断奇偶
            if(t)add(S,MD(k),INF,0,0);
            else add(MD(k),T,INF,0,0);
            if(i)add(DO(k-m),UP(k),1,0,t);
            if(j)add(RI(k-1),LE(k),1,0,t);
            in(shape);
            if(shape&1)add(UP(k),MD(k),1,0,t),++totf;//统计总流量
            if(shape&2)add(RI(k),MD(k),1,0,t),++totf;//因为每个流拆成了两段
            if(shape&4)add(DO(k),MD(k),1,0,t),++totf;//所以最终结果会是实际的两倍
            if(shape&8)add(LE(k),MD(k),1,0,t),++totf;//中点与四周点连边
            switch(shape)
            {
            case 8:++turn;//1000 ←
            case 4:++turn;//0100 ↓
            case 2:++turn;//0010 →
            case 1:       //0001 ↑
                add(RI(k),UP(k),1,1,t);
                add(DO(k),UP(k),1,2,t);
                add(LE(k),UP(k),1,1,t);
                break;//这四种形状内部连边情况是一样的,转一下统一处理就方便些了,下面同理
            case 9:++turn; //1001 ┘
            case 12:++turn;//1100 ┐
            case 6:++turn; //0110 ┌
            case 3:        //0011 └
                add(DO(k),UP(k),1,1,t);
                add(LE(k),RI(k),1,1,t);
                break;
            case 13:++turn;//1101 ┤
            case 14:++turn;//1110 ┬
            case 7:++turn; //0111 ├
            case 11:       //1011 ┴
                add(DO(k),LE(k),1,1,t);
                add(DO(k),UP(k),1,2,t);
                add(DO(k),RI(k),1,1,t);
                break;
            }
        }
    while(spfa())
    {
        m=INF;//这里m记下流量
        for(i=T;i!=S;i=to[k^1])
        {
            k=pre[i];
            if(m>f[k])m=f[k];
        }
        mf+=m;
        for(i=T;i!=S;i=to[k^1])
        {
            k=pre[i];
            f[k]-=m;f[k^1]+=m;
            mc+=m*c[k];
        }
    }
    printf("%d",totf==mf<<1?mc:-1);//注意如果没能流满就输-1
    return 0;
}

原文地址:https://www.cnblogs.com/flashhu/p/8277512.html

时间: 2024-11-05 16:10:37

洛谷P4003 无限之环(infinityloop)(网络流,费用流)的相关文章

洛谷 P4012 深海机器人问题【费用流】

题目链接:https://www.luogu.org/problemnew/show/P4012 洛谷 P4012 深海机器人问题 输入输出样例 输入样例#1: 1 1 2 2 1 2 3 4 5 6 7 2 8 10 9 3 2 0 0 2 2 2 输出样例#1: 42 说明 题解:建图方法如下: 对于矩阵中的每个点,向东.向北分别与其相邻点都要连两条边(重边): 1)容量为1,费用为该边价值的边: 2)容量为INF,费用为0的边(因为多个深海机器人可以在同一时间占据同一位置). 对于每个起点

POJ训练计划3422_Kaka&#39;s Matrix Travels(网络流/费用流)

解题报告 题目传送门 题意: 从n×n的矩阵的左上角走到右下角,每次只能向右和向下走,走到一个格子上加上格子的数,可以走k次.问最大的和是多少. 思路: 建图:每个格子掰成两个点,分别叫"出点","入点", 入点到出点间连一个容量1,费用为格子数的边,以及一个容量∞,费用0的边. 同时,一个格子的"出点"向它右.下的格子的"入点"连边,容量∞,费用0. 源点向(0,0)的入点连一个容量K的边,(N-1,N-1)的出点向汇点连一

POJ训练计划2516_Minimum Cost(网络流/费用流)

解题报告 题意: 有n个商店,m个提供商,k种商品</span> n*k的矩阵,表示每个商店需要每个商品的数目: m*k矩阵,表示每个提供商拥有每个商品的个数 然后对于每个物品k,都有n*m的矩阵 i行j列表示 从j提供商向i商店运送一个k商品的代价是多少 判断所有的仓库能否满足所有客户的需求,如果可以,求出最少的运输总费用 思路: 建图的题,不能直接把所有信息建成图,因为n和m跟k都有关系,如果那样子建图的话,就要把k种拆成m类,每个仓库连向该仓库的第k种,然后再和n连线,有费用, 不过这样

POJ 2195 Going Home(网络流-费用流)

Going Home Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 17777   Accepted: 9059 Description On a grid map there are n little men and n houses. In each unit time, every little man can move one unit step, either horizontally, or vertical

POJ2135_Farm Tour(网络流/费用流)

解题报告 题目传送门 题意: 一个人有n个农场,他想从1到n去,有从n到1回来,要求路径最短,且没有走重复的路. 思路: 如果两次最短路感觉不行的,可以看成费用流,每一条路容量都是1,这样只要流量等于2就行了. 一次mcmf模版. #include <iostream> #include <cstring> #include <queue> #include <cstdio> #define inf 0x3f3f3f3f using namespace st

POJ训练计划2195_Going Home(网络流/费用流)

解题报告 题目传送门 思路: bfs建图跑一下费用流就行. #include <iostream> #include <cstdio> #include <cstring> #include <queue> #define inf 0x3f3f3f3f using namespace std; struct E { int v,cost,cap,next; } edge[100000]; int head[1000],cnt,dis[1000],pre[10

UVa10806_Dijkstra, Dijkstra.(网络流/费用流)(小白书图论专题)

解题报告 思路: 从s->t 再从t->s等同与s->t两次,要求每条路只能走一次,要求最小花费,让每一条边容量为1,跑跑费用流 只要跑出流量为2就结束. #include <iostream> #include <cstring> #include <cstdio> #include <queue> #define inf 0x3f3f3f3f #define N 5000 #define M 50000 using namespace

洛谷P4016 负载平衡问题(最小费用最大流)

题目描述 GG 公司有 nn 个沿铁路运输线环形排列的仓库,每个仓库存储的货物数量不等.如何用最少搬运量可以使 nn 个仓库的库存数量相同.搬运货物时,只能在相邻的仓库之间搬运. 输入输出格式 输入格式: 文件的第 11 行中有 11 个正整数 nn ,表示有 nn 个仓库. 第 22 行中有 nn 个正整数,表示 nn 个仓库的库存量. 输出格式: 输出最少搬运量. 输入输出样例 输入样例#1: 复制 5 17 9 14 16 4 输出样例#1: 复制 11 说明 1 \leq n \leq

洛谷 3381 【模板】最小费用最大流

https://www.luogu.org/problem/show?pid=3381 题目描述 如题,给出一个网络图,以及其源点和汇点,每条边已知其最大流量和单位流量费用,求出其网络最大流和在最大流情况下的最小费用. 输入输出格式 输入格式: 第一行包含四个正整数N.M.S.T,分别表示点的个数.有向边的个数.源点序号.汇点序号. 接下来M行每行包含四个正整数ui.vi.wi.fi,表示第i条有向边从ui出发,到达vi,边权为wi(即该边最大流量为wi),单位流量的费用为fi. 输出格式: 一