title
BZOJ 5120
LUOGU 4003
Description
曾经有一款流行的游戏,叫做InfinityLoop,先来简单的介绍一下这个游戏:
游戏在一个n×m的网格状棋盘上进行,其中有些小方格中会有水管,水管可能在方格某些方向的边界的中点有接口,所有水管的粗细都相同,所以如果两个相邻方格的公共边界的中点都有接头,那么可以看作这两个接头互相连接。水管有以下15种形状:
游戏开始时,棋盘中水管可能存在漏水的地方。
形式化地:如果存在某个接头,没有和其它接头相连接,那么它就是一个漏水的地方。
玩家可以进行一种操作:选定一个含有非直线型水管的方格,将其中的水管绕方格中心顺时针或逆时针旋转90度。
直线型水管是指左图里中间一行的两种水管。
现给出一个初始局面,请问最少进行多少次操作可以使棋盘上不存在漏水的地方。
Input
第一行两个正整数n,m代表网格的大小。
接下来n行每行m数,每个数是[0,15]中的一个
你可以将其看作一个4位的二进制数,从低到高每一位分别代表初始局面中这个格子上、右、下、左方向上是否有水管接头。
特别地,如果这个数是000,则意味着这个位置没有水管。
比如3(0011(2))代表上和右有接头,也就是一个L型,而12(1100(2))代表下和左有接头,也就是将L型旋转180度。
n×m≤2000
Output
输出共一行,表示最少操作次数。如果无法达成目标,输出-1
Sample Input
2 3
3 14 12
3 11 12
Sample Output
2
HINT
样例1棋盘如下
旋转方法很显然,先将左上角虚线方格内的水管顺时针转90度
然后右下角虚线方格内的水管逆时针旋转90度,这样就使得水管封闭了
analysis
⚡cdecl⚡,他画的图是真好呀。
讲解的话,其实 FlashHu 大佬那里就好了。
我也不愿意为了这个网络流大神仙题目把图全搞一遍,而且听说这道题 \(40pts\) 可以用 \(插头dp\) 搞掉(不过我不会耶,( ̄︶ ̄)/ ),据说还能用 \(插头dp+map\) 卡过(好神仙)。
吐槽还是不能少的:
- 这道题第一眼看上去,谁能想到要用费用流?
- 费用流是 \(zkw\) 费用流。
所以就得得出一些网络流题目的判断方法了,来自 ⚡cdecl⚡ 的判断网络流的一个办法(好像真是这么回事):
- 网格图 / 棋盘 / 有规律的图案;
- 有一些与暴力算法无关的奇怪性质;
- 这道题:是棋盘;直管不能翻转。鉴定完毕。
code
#include<bits/stdc++.h>
#define c(k) k+(sum<<2)
#define u(k) k+turn*sum
#define r(k) k+((turn+1)&3)*sum
#define d(k) k+((turn+2)&3)*sum
#define l(k) k+((turn+3)&3)*sum
using namespace std;
const int maxn=1e5+10,maxm=1e6+10,inf=0x3f3f3f3f;
char buf[1<<15],*fs,*ft;
inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
template<typename T>inline void read(T &x)
{
x=0;
T f=1, ch=getchar();
while (!isdigit(ch) && ch^'-') ch=getchar();
if (ch=='-') f=-1, ch=getchar();
while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
x*=f;
}
template<typename T>inline void write(T x)
{
if (!x) { putchar('0'); return ; }
if (x<0) putchar('-'), x=-x;
T num=0, ch[20];
while (x) ch[++num]=x%10+48, x/=10;
while (num) putchar(ch[num--]);
}
int ver[maxm],edge[maxm],Next[maxm],cost[maxm],head[maxn],len=1;
inline void add(int x,int y,int z,int c,int type)
{
if (type) swap(x,y);
ver[++len]=y,edge[len]=z,cost[len]=c,Next[len]=head[x],head[x]=len;
ver[++len]=x,edge[len]=0,cost[len]=-c,Next[len]=head[y],head[y]=len;
}
int s,t;
int dist[maxn];
bool vis[maxn];
inline bool spfa()
{
memset(dist,0x3f,sizeof(dist));
memset(vis,0,sizeof(vis));
queue<int>q;q.push(s);
dist[s]=0,vis[s]=1;
while (!q.empty())
{
int x=q.front();
q.pop();
vis[x]=0;
for (int i=head[x]; i; i=Next[i])
{
if (!edge[i]) continue;
int y=ver[i];
if (dist[y]>dist[x]+cost[i])
{
dist[y]=dist[x]+cost[i];
if (!vis[y]) q.push(y),vis[y]=1;
}
}
}
if (dist[t]==inf) return false;
else return true;
}
long long ans;
inline int get(int x,int low)
{
if (x==t) return low;
vis[x]=1;
int tmp=low;
for (int i=head[x]; i; i=Next[i])
{
int y=ver[i];
if (edge[i] && dist[y]==dist[x]+cost[i] && !vis[y])
{
int a=get(y,min(tmp,edge[i]));
if (!a) dist[y]=0;
ans+=a*cost[i];
edge[i]-=a;
edge[i^1]+=a;
if (!(tmp-=a)) break;
}
}
vis[x]=0;
return low-tmp;
}
inline int NetFlow()
{
int flow,maxflow=0;
while (spfa())
{
flow=get(s,inf);
while (flow) maxflow+=flow,flow=get(s,inf);
}
return maxflow;
}
int main()
{
int n,m;read(n);read(m);
int sum=n*m, cnt=0; s=0, t=sum*5+1;
for (int i=1; i<=n; ++i)
for (int j=1; j<=m; ++j)
{
int turn=0,shape;
int type=(i^j)&1;
int id=(i-1)*m+j;
read(shape);
if (type) add(s,c(id),inf,0,0);
else add(c(id),t,inf,0,0);
if (i>1) add(d(id-m),u(id),1,0,type);
if (j>1) add(r(id-1),l(id),1,0,type);
if (shape&1) add(u(id),c(id),1,0,type),++cnt;
if (shape&2) add(r(id),c(id),1,0,type),++cnt;
if (shape&4) add(d(id),c(id),1,0,type),++cnt;
if (shape&8) add(l(id),c(id),1,0,type),++cnt;
switch (shape)
{
case 8: ++turn;
case 4: ++turn;
case 2: ++turn;
case 1:
add(r(id),u(id),1,1,type);
add(d(id),u(id),1,2,type);
add(l(id),u(id),1,1,type);
break;
case 9: ++turn;
case 12: ++turn;
case 6: ++turn;
case 3:
add(d(id),u(id),1,1,type);
add(l(id),r(id),1,1,type);
break;
case 13: ++turn;
case 14: ++turn;
case 7: ++turn;
case 11:
add(d(id),l(id),1,1,type);
add(d(id),u(id),1,2,type);
add(d(id),r(id),1,1,type);
break;
}
}
cnt>>=1;
if (NetFlow()==cnt) write(ans),puts("");
else puts("-1");
return 0;
}