一、题目
二、解法
真的是神题,我肝了半天才肝出来。
我们应该如何理解这个使棋盘上不存在漏水的地方
的含义?可以把这个矩阵黑白染色,让源点连白点,黑点连汇点,所求得的最大流
×
2
\times 2
×2需要等于接头的个数。本题要求操作次数最小,就可以理解为最小费用最大流。
对于矩阵中的每个点我们建 5 5 5个点,分别表示四个方向的接头和控制流量的中心点,难点在于旋转如何建图,我们考虑其内部的变化。
情况1:只有一处接口
A
|
D O B
C
如图,我们可以建三条边,分别是:
A
−
B
,
A
−
C
,
A
−
D
A-B,A-C,A-D
A−B,A−C,A−D,权值为
1
1
1,容量为
1
1
1,接在
A
A
A上可以限流。
情况2:有两处接口且水管非直线
A
|
D O --B
C
如图,我们可以建两条边,分别是:
A
−
C
,
B
−
D
A-C,B-D
A−C,B−D,权值为
1
1
1,容量为
1
1
1,为了理解它的正确性,我们可以分类讨论:
- 顺时针旋转 90 90 90,用到了 A − C A-C A−C这条边,花费正好为 1 1 1。
- 逆时针旋转 90 90 90,用到了 B − D B-D B−D这条边,花费正好为 1 1 1。
- 顺时针旋转 180 180 180,同时用到了 A − C , B − D A-C,B-D A−C,B−D,花费为 2 2 2。
情况3:有三处接口
A
|
D O --B
|
C
如图,我们可以见三条边,分别是:
A
−
D
(
1
,
1
)
,
C
−
D
(
1
,
1
)
,
B
−
D
(
2
,
1
)
A-D(1,1),C-D(1,1),B-D(2,1)
A−D(1,1),C−D(1,1),B−D(2,1)(前者为费用,后者为流量。)
其他情况我们就不需要旋转了,这里有几个细节,对于实现此题很重要:
- 要弄清楚顺序,本题一定要让白点连黑点。
- 0 , 1 , 2 , 3 0,1,2,3 0,1,2,3代表上右下左,对于每个点向外连边是要用这样的顺序。
- 即使一个点没有接口也要给四个方向编号,要不然建边会出问题。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
#include <queue>
using namespace std;
#define inf 0x3f3f3f3f
const int MAXN = 10005;
int read()
{
int num=0,flag=1;
char c;
while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
while(c>='0'&&c<='9')num=(num<<3)+(num<<1)+(c^48),c=getchar();
return num*flag;
}
int n,m,tot=1,sum,cnt,S,T,f[MAXN],bit[20],num[MAXN][5];
int flow[MAXN],dis[MAXN],pre[MAXN],lst[MAXN];
int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};//上右下左
struct edge
{
int v,c,f,next;
}e[MAXN*100];
struct node
{
int u,c;
};
queue<node> q;
void add_edge(int u,int v,int c,int fl,int ty=1)
{
if(!ty) swap(u,v);
e[++tot]=edge{v,c,fl,f[u]},f[u]=tot;
e[++tot]=edge{u,-c,0,f[v]},f[v]=tot;
}
bool bfs()
{
memset(dis,0x3f,sizeof dis);
flow[S]=inf;
dis[S]=0;
pre[S]=-1;
q.push(node{S,0});
while(!q.empty())
{
int u=q.front().u,t=q.front().c;
q.pop();
if(dis[u]<t) continue;
for(int i=f[u]; i; i=e[i].next)
{
int v=e[i].v,c=e[i].c;
if(dis[v]>dis[u]+c && e[i].f>0)
{
dis[v]=dis[u]+c;
pre[v]=u;
lst[v]=i;
flow[v]=min(flow[u],e[i].f);
q.push(node{v,dis[v]});
}
}
}
return dis[T]!=inf;
}
void add(int x,int y,int c)
{
sum+=bit[c];
int z=(x-1)*m+y,ty=(x+y)&1;
for(int i=0;i<=4;i++)
num[z][i]=++cnt;
if(bit[c]==0) return;
if(ty) add_edge(S,num[z][4],0,bit[c]);
else add_edge(num[z][4],T,0,bit[c]);
for(int i=0;i<4;i++)
if(c&(1<<i))
add_edge(num[z][4],num[z][i],0,1,ty);
if(bit[c]==1)
{
for(int i=0;i<4;i++)
if(c&(1<<i))
{
add_edge(num[z][i],num[z][(i+1)%4],1,1,ty);
add_edge(num[z][i],num[z][(i+2)%4],2,1,ty);
add_edge(num[z][i],num[z][(i+3)%4],1,1,ty);
}
}
if(bit[c]==2)
{
for(int i=0;i<4;i++)
if(c&(1<<i) && c&(1<<((i+2)%4)))
return ;
for(int i=0;i<4;i++)
if(c&(1<<i))
add_edge(num[z][i],num[z][(i+2)%4],1,1,ty);
}
if(bit[c]==3)
{
int t=0;
for(int i=0;i<4;i++)
if(!(c&(1<<i)))
t=i;
for(int i=0;i<4;i++)
if(c&(1<<i))
{
if(i==(t+2)%4) add_edge(num[z][i],num[z][t],2,1,ty);
else add_edge(num[z][i],num[z][t],1,1,ty);
}
}
}
void get()
{
int res=0,cost=0;
while(bfs())
{
res+=flow[T];
cost+=dis[T]*flow[T];
int cur=T;
while(cur!=S)
{
e[lst[cur]].f-=flow[T];
e[lst[cur]^1].f+=flow[T];
cur=pre[cur];
}
}
if(res*2!=sum) puts("-1");
else printf("%d\n",cost);
}
int main()
{
n=read();m=read();
S=++cnt;T=++cnt;
for(int i=1;i<=15;i++)
bit[i]=bit[i>>1]+(i&1);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
add(i,j,read());
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 tx=i+dx[k],ty=j+dy[k];
if(tx>=1 && tx<=n && ty>=1 && ty<=m)
add_edge(num[(i-1)*m+j][k],num[(tx-1)*m+ty][(k+2)%4],0,1);
}
get();
}