题解 P4003 【无限之环】
钱逸凡
2018-08-12 15:59:59
~~打了几个小时终于A了(我真是太蒻了),第一次A黑题,兴奋地发了个题解~~
[推荐一波我的博客](https://www.luogu.org/blog/ONE-PIECE/)
# 很好的网络流题
本题是费用流。(根本看不出来)
### 首先要知道怎么判断图是否漏水。
方法如下:
#### 考虑黑白染色法:
把网格染成黑白相间的格子,把每一个白色当成源点,每一个黑色当成汇点。
把每个格子周围建立四个点,白色(源点)连向能通的周围点,黑色周围点连向汇点。
把白色的周围点连向黑色周围点。
什么意思呢?
以下图为例:![](https://cdn.luogu.com.cn/upload/pic/28114.png)
对于一张不会漏的图,如:(画图技术有限)
![](https://cdn.luogu.com.cn/upload/pic/28117.png)
我们连以下边:
![](https://cdn.luogu.com.cn/upload/pic/28118.png)
这时,满流就等价于不会漏。
### 接下来考虑旋转的问题:
先找一张会漏但经过旋转就不会漏的图:
![](https://cdn.luogu.com.cn/upload/pic/28120.png)
将左上角旋转一次就不会漏。
那转化回黑白图是什么呢?
![](https://cdn.luogu.com.cn/upload/pic/28123.png)
为了满足旋转,我们加入一条蓝色的边,代价为1。
然后我们跑费用流,红色的流量为1,代价为0,蓝色的流量为1,代价为1,答案即是1,也就是需要旋转的次数。
讲到这里,大概你就懂了。
没错,只要把每个格子对应的红边蓝边加好,跑费用流即可。
题目里给的水管可以分为5类:
### 以下全部默认白格,黑格所有边反向
1.直线型:
![](https://cdn.luogu.com.cn/upload/pic/28128.png):
由于不用旋转,只需把对应的红边加好,既s指向上和s指向下。
2.Q型:
![](https://cdn.luogu.com.cn/upload/pic/28129.png)
对于这种,我们需要加入一条红边,从s(源点)指向初始方向(以向上为例),加入蓝边,上指向左,代价为1,上指向右,代价为1,上指向下,代价为2(要旋转两次)。
3.丁型:
![](https://cdn.luogu.com.cn/upload/pic/28130.png)
我们需要加入三条红边:s指向上,s指向左,s指向右,
还有三条蓝边:上指向下,代价为2(旋转两次),左指向下,代价为1(旋转一次),右指向下,代价为1(旋转一次)。//这个地方旋转要自己画图理解
4.L型:
![](https://cdn.luogu.com.cn/upload/pic/28131.png)
我们需要加入两条红边:s指向上,s指向右,再加入两条蓝边:上指向下,代价为1,(旋转一次,因为旋转一次后,仍有指向右的,故只需改上的)右指向左,代价为1。
是不是很奇怪,没有代价为2的边,其实旋转两次相当于两条蓝边都走,总代价就是2了。
5.+型:
![](https://cdn.luogu.com.cn/upload/pic/28134.png)
由于这种不能旋转,直接加4条红边即可。
这题的难点在于建图,图建好了跑费用流就能过了。
附上我丑陋的代码:
打了三百多行累死我了,不过其实很多情况可以合并的。
```
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cstdio>
using namespace std;
const int inf=1<<30;
int Read(){
int x=0,f=1;
char c=getchar();
while(c>'9'||c<'0'){
if(c=='-')f=-1;
c=getchar();
}
while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();
return f==1?x:-x;
}
int maxflow=0;
int mincost=0;
int realflow=0;
int n,m;
int top=1,head[101010],inque[101010];
int s,t;
int dist[101010];
struct Node{
int v;
int val;
int next;
int cost;
}node[101010];
void addedge(int u,int v,int val,int cost){
node[++top].v=v;
node[top].val=val;
node[top].cost=cost;
node[top].next=head[u];
head[u]=top;
}//加边函数
void add(int u,int v,int val,int cost){
addedge(u,v,val,cost);
addedge(v,u,0,-cost);
}//网络流的加边,正向和反向
bool spfa(){
memset(inque,0,sizeof(inque));
memset(dist,0x3f,sizeof(dist));
dist[s]=0;
inque[s]=1;
queue<int>q;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
inque[u]=0;
for(int i=head[u];i;i=node[i].next){
int d=node[i].v;
if(dist[d]>dist[u]+node[i].cost&&node[i].val){
dist[d]=dist[u]+node[i].cost;
if(inque[d]==0){
q.push(d);
inque[d]=1;
}
}
}
}
return dist[t]!=0x3f3f3f3f;
}
inline int min(int x,int y){
return x<y?x:y;
}
int dfs(int u,int low){
if(u==t){inque[t]=1;maxflow+=low;return low;}
int used=0;
inque[u]=1;
for(int i=head[u];i;i=node[i].next){
int d=node[i].v;
if((inque[d]==0||d==t)&&node[i].val!=0&&dist[d]==dist[u]+node[i].cost){
int minflow=dfs(d,min(low-used,node[i].val));
if(minflow!=0)
mincost+=node[i].cost*minflow,node[i].val-=minflow,node[i^1].val+=minflow,used+=minflow;
if(used==low)break;
}
}
return used;
}
int Dinic(){
while(spfa()){
inque[t]=1;
while(inque[t]){
memset(inque,0,sizeof(inque));
dfs(s,inf);
}
}
return maxflow;
}
inline bool check(int i,int j){
return i>0&&i<=n&&j>0&&j<=m;
}//判断(i,j)是否需要加节点
inline int up(int i,int j){
return 4*((i-1)*m+j);
}//(i,j)的上节点
inline int down(int i,int j){
return 4*((i-1)*m+j)+1;
}//(i,j)的下节点
inline int left(int i,int j){
return 4*((i-1)*m+j)+2;
}//(i,j)的左节点
inline int right(int i,int j){
return 4*((i-1)*m+j)+3;
}//(i,j)的右节点
int main() {
//freopen("cin.txt","r",stdin);
s=10010,t=10001;
n=Read(),m=Read();
int i,j;
int map,colur;
colur=0;
for(i=1;i<=n;i++){
colur=i%2;
for(j=1;j<=m;j++){
map=Read();
colur^=1;
if(colur==0){
if(check(i,j-1))add(left(i,j),right(i,j-1),1,0);
if(check(i,j+1))add(right(i,j),left(i,j+1),1,0);
if(check(i-1,j))add(up(i,j),down(i-1,j),1,0);
if(check(i+1,j))add(down(i,j),up(i+1,j),1,0);
}//白色点四周与附近的黑色点四周相连
if(map==1){//0001
realflow++;
if(colur==0){
add(s,up(i,j),1,0);
add(up(i,j),right(i,j),1,1);//向右流要转一次,代价为1
add(up(i,j),left(i,j),1,1);//向左代价为1
add(up(i,j),down(i,j),1,2);//向下代价为2
}//白色点s向四周流
if(colur==1){
add(up(i,j),t,1,0);
add(right(i,j),up(i,j),1,1);
add(left(i,j),up(i,j),1,1);
add(down(i,j),up(i,j),1,2);
}//黑色点四周向t流
continue;
}
if(map==2){//0010
realflow++;
if(colur==0){
add(s,right(i,j),1,0);
add(right(i,j),up(i,j),1,1);
add(right(i,j),down(i,j),1,1);
add(right(i,j),left(i,j),1,2);
}
if(colur==1){
add(right(i,j),t,1,0);
add(up(i,j),right(i,j),1,1);
add(down(i,j),right(i,j),1,1);
add(left(i,j),right(i,j),1,2);
}
continue;
}
if(map==3){//0011
realflow+=2;
if(colur==0){
add(s,up(i,j),1,0);
add(s,right(i,j),1,0);
add(up(i,j),down(i,j),1,1);
add(right(i,j),left(i,j),1,1);
}
if(colur==1){
add(up(i,j),t,1,0);
add(right(i,j),t,1,0);
add(down(i,j),up(i,j),1,1);
add(left(i,j),right(i,j),1,1);
}
continue;
}
if(map==4){//0100
realflow++;
if(colur==0){
add(s,down(i,j),1,0);
add(down(i,j),right(i,j),1,1);
add(down(i,j),left(i,j),1,1);
add(down(i,j),up(i,j),1,2);
}
if(colur==1){
add(down(i,j),t,1,0);
add(right(i,j),down(i,j),1,1);
add(left(i,j),down(i,j),1,1);
add(up(i,j),down(i,j),1,2);
}
continue;
}
if(map==5){//0101
realflow+=2;
if(colur==0){
add(s,up(i,j),1,0);
add(s,down(i,j),1,0);
}
if(colur==1){
add(up(i,j),t,1,0);
add(down(i,j),t,1,0);
}
}//直线型不可旋转
if(map==6){//0110
realflow+=2;
if(colur==0){
add(s,right(i,j),1,0);
add(s,down(i,j),1,0);
add(right(i,j),left(i,j),1,1);
add(down(i,j),up(i,j),1,1);
}
if(colur==1){
add(right(i,j),t,1,0);
add(down(i,j),t,1,0);
add(left(i,j),right(i,j),1,1);
add(up(i,j),down(i,j),1,1);
}
}
if(map==7){//0111
realflow+=3;
if(colur==0){
add(s,up(i,j),1,0);
add(s,right(i,j),1,0);
add(s,down(i,j),1,0);
add(up(i,j),left(i,j),1,1);
add(down(i,j),left(i,j),1,1);
add(right(i,j),left(i,j),1,2);
}
if(colur==1){
add(up(i,j),t,1,0);
add(right(i,j),t,1,0);
add(down(i,j),t,1,0);
add(left(i,j),up(i,j),1,1);
add(left(i,j),down(i,j),1,1);
add(left(i,j),right(i,j),1,2);
}
}
if(map==8){//1000
realflow++;
if(colur==0){
add(s,left(i,j),1,0);
add(left(i,j),up(i,j),1,1);
add(left(i,j),down(i,j),1,1);
add(left(i,j),right(i,j),1,2);
}
if(colur==1){
add(left(i,j),t,1,0);
add(up(i,j),left(i,j),1,1);
add(down(i,j),left(i,j),1,1);
add(right(i,j),left(i,j),1,2);
}
}
if(map==9){//1001
realflow+=2;
if(colur==0){
add(s,up(i,j),1,0);
add(s,left(i,j),1,0);
add(up(i,j),down(i,j),1,1);
add(left(i,j),right(i,j),1,1);
}
if(colur==1){
add(up(i,j),t,1,0);
add(left(i,j),t,1,0);
add(down(i,j),up(i,j),1,1);
add(right(i,j),left(i,j),1,1);
}
}
if(map==10){//1010
realflow+=2;
if(colur==0){
add(s,left(i,j),1,0);
add(s,right(i,j),1,0);
}
if(colur==1){
add(left(i,j),t,1,0);
add(right(i,j),t,1,0);
}
continue;//直线型
}
if(map==11){//1011
realflow+=3;
if(colur==0){
add(s,up(i,j),1,0);
add(s,left(i,j),1,0);
add(s,right(i,j),1,0);
add(left(i,j),down(i,j),1,1);
add(right(i,j),down(i,j),1,1);
add(up(i,j),down(i,j),1,2);
}
if(colur==1){
add(up(i,j),t,1,0);
add(left(i,j),t,1,0);
add(right(i,j),t,1,0);
add(down(i,j),left(i,j),1,1);
add(down(i,j),right(i,j),1,1);
add(down(i,j),up(i,j),1,2);
}
}
if(map==12){//1100
realflow+=2;
if(colur==0){
add(s,left(i,j),1,0);
add(s,down(i,j),1,0);
add(left(i,j),right(i,j),1,1);
add(down(i,j),up(i,j),1,1);
}
if(colur==1){
add(left(i,j),t,1,0);
add(down(i,j),t,1,0);
add(right(i,j),left(i,j),1,1);
add(up(i,j),down(i,j),1,1);
}
}
if(map==13){//1101
realflow+=3;
if(colur==0){
add(s,up(i,j),1,0);
add(s,down(i,j),1,0);
add(s,left(i,j),1,0);
add(up(i,j),right(i,j),1,1);
add(down(i,j),right(i,j),1,1);
add(left(i,j),right(i,j),1,2);
}
if(colur==1){
add(up(i,j),t,1,0);
add(down(i,j),t,1,0);
add(left(i,j),t,1,0);
add(right(i,j),up(i,j),1,1);
add(right(i,j),down(i,j),1,1);
add(right(i,j),left(i,j),1,2);
}
}
if(map==14){//1110
realflow+=3;
if(colur==0){
add(s,left(i,j),1,0);
add(s,down(i,j),1,0);
add(s,right(i,j),1,0);
add(left(i,j),up(i,j),1,1);
add(right(i,j),up(i,j),1,1);
add(down(i,j),up(i,j),1,2);
}
if(colur==1){
add(left(i,j),t,1,0);
add(down(i,j),t,1,0);
add(right(i,j),t,1,0);
add(up(i,j),left(i,j),1,1);
add(up(i,j),right(i,j),1,1);
add(up(i,j),down(i,j),1,2);
}
}
if(map==15){//1111
realflow+=4;
if(colur==0){
add(s,up(i,j),1,0);
add(s,right(i,j),1,0);
add(s,left(i,j),1,0);
add(s,down(i,j),1,0);
}
if(colur==1){
add(up(i,j),t,1,0);
add(down(i,j),t,1,0);
add(left(i,j),t,1,0);
add(right(i,j),t,1,0);
}
}
}
}
Dinic();
if(maxflow*2!=realflow)printf("-1");
else printf("%d",mincost);
return 0;
}
```
需要注意的是,无论黑白,在统计应该的流量(代码中为realflow)时一视同仁,最后只需最大流等于应该的流量即为满流。
# 看懂了就给个赞吧。
没看懂可以发讨论。