无限之环
题目
链接
描述
曾经有一款流行的游戏,叫做 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;
}
请注意:
对上下左右的编号很重要!
注意判断不可行的情况!
总结(讲垃圾话)
趁着放假在家做完了这道题,用了大半天,还是有很多感想。
把思路落在笔尖上。
怎么编号,怎么建图,不能只说,写写画画也很重要。