无限之环
题目描述
曾经有一款流行的游戏,叫做 Infinity Loop,先来简单的介绍一下这个游戏:
游戏在一个 n×m 的网格状棋盘上进行,其中有些小方格中会有水管,水管可能在方格某些方向的边界的中点有接口,所有水管的粗细都相同,所以如果两个相邻方格的公共边界的中点都有接头,那么可以看作这两个接头互相连接。水管有以下 15 种形状:
游戏开始时,棋盘中水管可能存在漏水的地方。
形式化地:如果存在某个接头,没有和其它接头相连接,那么它就是一个漏水的地方。
玩家可以进行一种操作:选定一个含有非直线型水管的方格,将其中的水管绕方格中心顺时针或逆时针旋转 90 度。
直线型水管是指左图里中间一行的两种水管。
现给出一个初始局面,请问最少进行多少次操作可以使棋盘上不存在漏水的地方。
输入格式
第一行两个正整数 n,m ,代表网格的大小。
接下来
n
行每行
特别地,如果这个数是
比如 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×m≤2000
这建图差评……
建图部分代码又臭又长……
(虽然不难调)
思路:
满足联通的情况下费用最小,那么就是最小费用最大流了~
考虑把每个节点拆成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;
}