原题链接:https://loj.ac/problem/2321
无限之环
题目描述
曾经有一款流行的游戏,叫做 Infinity Loop \text{Infinity Loop} Infinity Loop,先来简单的介绍一下这个游戏:
游戏在一个 n × m n \times m n×m的网格状棋盘上进行,其中有些小方格中会有水管,水管可能在方格某些方向的边界的中点有接口,所有水管的粗细都相同,所以如果两个相邻方格的公共边界的中点都有接头,那么可以看作这两个接头互相连接。水管有以下 15 15 15 种形状:
游戏开始时,棋盘中水管可能存在漏水的地方。
形式化地:如果存在某个接头,没有和其它接头相连接,那么它就是一个漏水的地方。
玩家可以进行一种操作:选定一个含有非直线型水管的方格,将其中的水管绕方格中心顺时针或逆时针旋转 90 90 90 度。
直线型水管是指左图里中间一行的两种水管。
现给出一个初始局面,请问最少进行多少次操作可以使棋盘上不存在漏水的地方。
输入格式
第一行两个正整数 n , m n,m n,m,代表网格的大小。
接下来 n n n 行每行 m m m 个数,每个数是 [ 0 , 15 ] [0,15] [0,15] 中的一个,你可以将其看作一个 4 4 4 位的二进制数,从低到高每一位分别代表初始局面中这个格子上、右、下、左方向上是否有水管接头。
特别地,如果这个数是 0 0 0,则意味着这个位置没有水管。
比如 3 ( 001 1 ( 2 ) ) 3(0011_{(2)}) 3(0011(2))代表上和右有接头,也就是一个 L 型,而 12 ( 110 0 ( 2 ) ) 12(1100_{(2)}) 12(1100(2)) 代表下和左有接头,也就是将 L 型旋转 180 180 180度。
输出格式
输出共一行,表示最少操作次数。如果无法达成目标,输出 − 1 -1 −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 1,超级源点汇点都连 i n f \mathcal{inf} inf,跑一遍最大流,查看格点内部的边是否满流就能知道有没有“漏水”了。
这道题要求的是转动次数,我们可以理解为“费用”,下面以黑色格子为例,冷静分析一下(白色的反着连就好了):
考虑度数为 1 1 1的管子:
顺时针旋转 π 2 \frac{\pi}{2} 2π:
发现对于一个旋转操作,相当于从 B B B向 C C C连了一条容量为 1 1 1,费用为 1 1 1边。
而旋转 π \pi π就相当于从 B B B向 D D D连了一条容量为 1 1 1,费用为 2 2 2边。
类比一下,我们就能得到其他管子的连边。
度数为 2 2 2时:
顺时针旋转 π 2 \frac{\pi}{2} 2π时:
c a p ( A → C ) = 1 , c o s t ( A → C ) = 1 cap(A\to C)=1,cost(A\to C)=1 cap(A→C)=1,cost(A→C)=1
同理,逆时针旋转 π 2 \frac{\pi}{2} 2π时:
c a p ( B → D ) = 1 , c o s t ( B → D ) = 1 cap(B\to D)=1,cost(B\to D)=1 cap(B→D)=1,cost(B→D)=1
而旋转 π \pi π时就相当于上面两个操作加和。
度数为 3 3 3时:
顺时针旋转 π 2 \frac{\pi}{2} 2π时:
c a p ( A → D ) = 1 , c o s t ( A → D ) = 1 cap(A\to D)=1,cost(A\to D)=1 cap(A→D)=1,cost(A→D)=1
同理,逆时针旋转 π 2 \frac{\pi}{2} 2π时:
c a p ( C → D ) = 1 , c o s t ( C → D ) = 1 cap(C\to D)=1,cost(C\to D)=1 cap(C→D)=1,cost(C→D)=1
旋转 π \pi π时:
c a p ( B → D ) = 1 , c o s t ( B → D ) = 2 cap(B\to D)=1,cost(B\to D)=2 cap(B→D)=1,cost(B→D)=2
这样也很好理解为什么不资瓷旋转直线型了,因为直线只有一种旋转方式不能加和,并且一次更改了两个接口,没办法用上面的方法建图。
连完边跑费用流就好了,打板贼 6 6 6。
代码
看到第一行代码,大家应该知道我经历了什么。。。
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
const int M=25000,N=1e5,G=2005;
struct sd{int to,fl,val;}ed[M];
struct cd{int v,w,a,s,d;}pt[N];
int n,m,tot,id,totf,maxf,cost,start,end,edge[9],dis[N],minf[N],pre[N],val[G][G];
bool vis[N];
vector<int>mmp[N];
deque<int>dui;
void add(int f,int t,int fl,int val,int typ)
{
mmp[f].push_back(id);ed[id++]=(sd){t,(typ?fl:0),(typ?val:-val)};
mmp[t].push_back(id);ed[id++]=(sd){f,(typ?0:fl),(typ?-val:val)};
}
void link(int x,int y,int val)
{
if(!val)return;
int pos=(x-1)*m+y,w,a,s,d,v,du=0,p,typ=(x+y)%2;
memset(edge,0,sizeof(edge));
for(int i=0;i<=3;++i){p=1<<i;if(val&p)edge[i]=1,++du;}
w=pt[pos].w;a=pt[pos].a;s=pt[pos].s;d=pt[pos].d;
if(typ)
{
if(x>1)add(w,pt[pos-m].s,1,0,1);if(x<n)add(s,pt[pos+m].w,1,0,1);
if(y>1)add(a,pt[pos-1].d,1,0,1);if(y<m)add(d,pt[pos+1].a,1,0,1);
if(edge[0])add(start,w,1,0,1),++totf;if(edge[1])add(start,d,1,0,1),++totf;
if(edge[2])add(start,s,1,0,1),++totf;if(edge[3])add(start,a,1,0,1),++totf;
}
else
{
if(edge[0])add(w,end,1,0,1),++totf;if(edge[1])add(d,end,1,0,1),++totf;
if(edge[2])add(s,end,1,0,1),++totf;if(edge[3])add(a,end,1,0,1),++totf;
}
if(du==1)
{
if(edge[0])add(w,a,1,1,typ),add(w,d,1,1,typ),add(w,s,1,2,typ);
else if(edge[1])add(d,w,1,1,typ),add(d,s,1,1,typ),add(d,a,1,2,typ);
else if(edge[2])add(s,a,1,1,typ),add(s,d,1,1,typ),add(s,w,1,2,typ);
else if(edge[3])add(a,s,1,1,typ),add(a,w,1,1,typ),add(a,d,1,2,typ);
}
else if(du==2)
{
if((edge[0]&edge[2])||(edge[1]&edge[3]))return;
if(edge[0])add(w,s,1,1,typ);if(edge[1])add(d,a,1,1,typ);
if(edge[2])add(s,w,1,1,typ);if(edge[3])add(a,d,1,1,typ);
}
else if(du==3)
{
if(!edge[0])add(a,w,1,1,typ),add(d,w,1,1,typ),add(s,w,1,2,typ);
else if(!edge[1])add(w,d,1,1,typ),add(s,d,1,1,typ),add(a,d,1,2,typ);
else if(!edge[2])add(a,s,1,1,typ),add(d,s,1,1,typ),add(w,s,1,2,typ);
else if(!edge[3])add(w,a,1,1,typ),add(s,a,1,1,typ),add(d,a,1,2,typ);
}
}
void build()
{
int pos;
for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)
{
if(!val[i][j])continue;
pos=(i-1)*m+j,pt[pos].w=++tot,pt[pos].a=++tot,pt[pos].s=++tot,pt[pos].d=++tot;
}
for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)link(i,j,val[i][j]);
}
void in()
{
int a;
scanf("%d%d",&n,&m);start=n*m*5+100,end=n*m*5+101;
for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)scanf("%d",&val[i][j]);
build();
}
bool spfa(int s,int e)
{
fill(dis,dis+1+tot,INT_MAX);dis[end]=INT_MAX;
memset(vis,0,sizeof(vis));
dui.push_front(s);vis[s]=1;dis[s]=0;minf[s]=inf;
int f,hh,to,fl,val;
while(!dui.empty())
{
f=dui.front();dui.pop_front();vis[f]=0;
for(int i=mmp[f].size()-1;i>=0;--i)
{
hh=mmp[f][i];to=ed[hh].to;fl=ed[hh].fl;val=ed[hh].val;
if(fl>0&&dis[to]>dis[f]+val)
{
dis[to]=dis[f]+val;minf[to]=min(minf[f],fl);pre[to]=hh;
if(!vis[to])dui.push_back(to),vis[to]=1;
}
}
}
return dis[e]!=INT_MAX;
}
void up(int s,int e)
{
int v=e,hh;
while(v!=s){hh=pre[v];ed[hh].fl-=minf[e];ed[hh^1].fl+=minf[e];v=ed[hh^1].to;}
maxf+=minf[e];cost+=minf[e]*dis[e];
}
void ac()
{
while(spfa(start,end))up(start,end);
printf("%d",maxf*2==totf?cost:-1);
}
int main(){in();ac();}