「清华集训 2017」无限之环

Posted miracevin

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了「清华集训 2017」无限之环相关的知识,希望对你有一定的参考价值。

无限之WA

https://www.luogu.org/problemnew/show/P4003

本题如果知道是网络流的话,其实建图不算特别神奇,但是比较麻烦。

数据范围过大,插头dp不能处理,而且是一个网格图,考虑网络流。

先看是不是二分图?

每个格子只会和相邻四个格子发生关系

所以,黑白染色正好。

i+j为偶数左部点,i+j为奇数右部点

不漏水是什么?

每个管道的四个口都能和别的接好。

S向左,右向T连口数的容量,费用为0的边

考虑怎么能把左右连在一起。

考虑要上下左右四个方向匹配,那么必然还要拆点(但是这些点之间是并列关系)。

每个本点向四个分点之间考虑连边(在这里考虑旋转以及费用)

考虑通过流量流过来表示选择旋转与否,恰当的边赋恰当的值。

sz=1:

自己方向是(1,0)相邻方向(1,1),对面(1,2)

sz=2:

直线型:自己方向(1,0),另外两个不连(因为不能转),

非直线型:自己两个方向(1,0),每个自己方向+2(-2),连(1,1),(手动模拟一下这样选择的四种流法对应四种旋转的位置)

sz=3:

自己(1,0),剩下一个0位置,距离为1的向它连(1,1),距离为2的向它连(1,2)

sz=4

四个方向(1,0);

(注意,右边的单向边方向和左边的完全相反)

 

然后左右分点之间相邻的关系上下,左右,下上,右左,左分点连到对应的右分点。

然后跑最小费用最大流。

 

至于-1

先判断黑格口数是不是等于白格口数

然后如果最大流不是口数(满流)的话,就-1

(其实数据没有-1的点23333~~~)

 

这样,一条流的意义是什么?左边的某个口和右边的某个口,通过旋转或者不旋转连接在了一起,同时两个管道的需求都少了1

由于所有边的流量都是1(除了和ST连的),所以每个口只会流出1流,减少1的需求,

如果最后满流

那么意味着,所有的口都满足了自己的需求,即每个口都连上了。而且每个流都合法。

 

出错点:

1.数组开小了。。。。has[4],四位二进制数,少开一位。。。(这个导致开O2之后超级厌氧,全部输出-1WA掉,一定程度上转移了查错重心。。。)

2.提取四位二进制数的时候,习惯性地写成了while(tmp) has[++tot]=tmp%2,tmp>>=1;然鹅,最高位是0的话,没有提取完4位就break了。而且has没有memset,高位就存上了之前可能的1.。。。。导致WA死。。

其实开始找规律一点没错。。。。但是由于while高位0的锅,以为找错了,,,最后还打了暴力判断。。。。

 

 

代码:(得开O2)

#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#include<bits/stdc++.h>
#define il inline
#define reg register int
#define numb (ch^‘0‘)
using namespace std;
typedef long long ll;
il void rd(int &x){
    char ch;bool fl=false;
    while(!isdigit(ch=getchar()))(ch==-)&&(fl=true);
    for(x=numb;isdigit(ch=getchar());x=x*10+numb);
    (fl==true)&&(x=-x);
}
namespace Miracle{
const int N=2000+2;
const int inf=0x3f3f3f3f;
int n,m,s,t;
struct node{
    int nxt,to;
    int w,v;
}e[(5*N+4*N+N*6)*2];
int hd[5*N],cnt=1;
void add(int x,int y,int w,int v){
    e[++cnt].nxt=hd[x];
    e[cnt].to=y;
    e[cnt].w=w;
    e[cnt].v=v;
    hd[x]=cnt;
    
    e[++cnt].nxt=hd[y];
    e[cnt].to=x;
    e[cnt].w=0;
    e[cnt].v=-v;
    hd[y]=cnt;
}
int incf[5*N],dis[5*N];
int pre[5*N];
bool vis[5*N];
queue<int>q;
bool spfa(){
    while(!q.empty()) q.pop();
    memset(vis,0,sizeof vis);
    memset(dis,inf,sizeof dis);
    dis[s]=0;
    incf[s]=inf;
    pre[s]=0;
    q.push(s);
    while(!q.empty()){
        int x=q.front();q.pop();
        vis[x]=0;
        for(reg i=hd[x];i;i=e[i].nxt){
            int y=e[i].to;
            if(e[i].w&&dis[y]>dis[x]+e[i].v){
                dis[y]=dis[x]+e[i].v;
                pre[y]=i;
                incf[y]=min(e[i].w,incf[x]);
                if(!vis[y]){
                    vis[y]=1;
                    q.push(y);
                }
            }    
        }
    }
    if(dis[t]==inf) return false;
    return true;
}
int ans,maxflow;
void upda(){
    int x=t;
    while(pre[x]){
        e[pre[x]].w-=incf[t];
        e[pre[x]^1].w+=incf[t];
        x=e[pre[x]^1].to;
    }
    ans+=incf[t]*dis[t];
    maxflow+=incf[t];
    //cout<<" ans "<<ans<<" "<<maxflow<<endl;
}
int has[10],tot;
int mp[N][N];
int sz[N];
int num(int x,int y,int k){//5 is itself
    return ((x-1)*m+y-1)*5+k;
}
int main(){
    rd(n);rd(m);
    s=0,t=n*m*5+1;
    for(reg i=1;i<=16;++i){
        sz[i]=sz[i>>1]+(i&1);
    }
    int le=0,ri=0;
    for(reg i=1;i<=n;++i){
        for(reg j=1;j<=m;++j){
            rd(mp[i][j]);
            if((i+j)%2==0) add(s,num(i,j,5),sz[mp[i][j]],0),le+=sz[mp[i][j]];
            else add(num(i,j,5),t,sz[mp[i][j]],0),ri+=sz[mp[i][j]];
        }
    }
    if(le!=ri){
        printf("-1");return 0;
    }
    for(reg i=1;i<=n;++i){
        for(reg j=1;j<=m;++j){
            for(reg l=1;l<=4;++l){
                has[l]=(mp[i][j]>>(l-1))&1;
            }
            int now=num(i,j,5);
            tot=sz[mp[i][j]];
            if((i+j)%2==0){
                switch(tot){
                    case 0:{
                        break;
                    }
                    case 1:{
                        int pos=0;
                        //cout<<" find "<<mp[i][j]<<" :: "<<has[1]<<" "<<has[2]<<" "<<has[3]<<" "<<has[4]<<endl;
                        for(reg l=1;l<=4;++l) if(has[l]) {
                            pos=l;
                        }
                        add(now,num(i,j,pos),1,0);
                        add(now,num(i,j,pos%4+1),1,1);
                        add(now,num(i,j,pos==1?4:pos-1),1,1);
                        add(now,num(i,j,(pos+2<=4)?pos+2:pos-2),1,2);
                        break;
                    }
                    case 2:{
                        if(mp[i][j]==5||mp[i][j]==10){
//    cout<<" dkfjdf "<<endl;
                            for(reg l=1;l<=4;++l){
                                if(has[l]) add(now,num(i,j,l),1,0);
                            }
                        }else{
                            for(reg l=1;l<=4;++l){
                                if(has[l]) {
                                    add(now,num(i,j,l),1,0);
                                    add(num(i,j,l),num(i,j,(l+2<=4)?l+2:l-2),1,1);
                                }
                            }
                        }
                        break;
                    }
                    case 3:{
                        for(reg l=1;l<=4;++l){
                            if(has[l]){
                                add(now,num(i,j,l),1,0);
                            }else{
                                add(num(i,j,(l+1)<=4?l+1:l-3),num(i,j,l),1,1);
                                add(num(i,j,(l+3)<=4?l+3:l-1),num(i,j,l),1,1);
                                add(num(i,j,(l+2)<=4?l+2:l-2),num(i,j,l),1,2);
                            }
                        }
                        break;
                    }
                    case 4:{
                        for(reg l=1;l<=4;++l){
                            add(now,num(i,j,l),1,0);
                        }
                        break;
                    }
                }
            }else{
                switch(tot){
                    case 0:{
                        break;
                    }
                    case 1:{
                        int pos=0;
                        for(reg l=1;l<=4;++l) if(has[l]){
                             pos=l;
                        }
                        add(num(i,j,pos),now,1,0);
                        add(num(i,j,pos%4+1),now,1,1);
                        add(num(i,j,pos==1?4:pos-1),now,1,1);
                        add(num(i,j,(pos+2<=4)?pos+2:pos-2),now,1,2);
                        break;
                    }
                    case 2:{
                        if(mp[i][j]==5||mp[i][j]==10){
                            for(reg l=1;l<=4;++l){
                                if(has[l]) add(num(i,j,l),now,1,0);
                            }
                        }else{
                            for(reg l=1;l<=4;++l){
                                if(has[l]) {
                                    add(num(i,j,l),now,1,0);
                                    add(num(i,j,(l+2<=4)?l+2:l-2),num(i,j,l),1,1);
                                }
                            }
                        }
                        break;
                    }
                    case 3:{
                        for(reg l=1;l<=4;++l){
                            if(has[l]){
                                add(num(i,j,l),now,1,0);
                            }else{
                                add(num(i,j,l),num(i,j,(l+1)<=4?l+1:l-3),1,1);
                                add(num(i,j,l),num(i,j,(l+3)<=4?l+3:l-1),1,1);
                                add(num(i,j,l),num(i,j,(l+2)<=4?l+2:l-2),1,2);
                            }
                        }
                        break;
                    }
                    case 4:{
                        for(reg l=1;l<=4;++l){
                            add(num(i,j,l),now,1,0);
                        }
                        break;
                    }
                }
            }
            if((i+j)%2==0){
                if(i>1) add(num(i,j,1),num(i-1,j,3),1,0);
                if(i<n)    add(num(i,j,3),num(i+1,j,1),1,0);
                if(j>1) add(num(i,j,4),num(i,j-1,2),1,0);
                if(j<m) add(num(i,j,2),num(i,j+1,4),1,0);
            } 
        }
    }
    while(spfa()) upda();
    //cout<<" maxflow "<<maxflow<<endl;
    if(maxflow!=le){
        puts("-1");return 0;
    }
    printf("%d",ans);
    return 0;
}

}
signed main(){
    Miracle::main();
    return 0;
}

/*
   Author: *Miracle*
   Date: 2018/12/14 21:08:15
*/

总结:

这个题的突破口的话,

发现插头dp不行,那么网格图,可能就是网络流(数据范围也支持)

黑白染色可以。那么考虑最终合法的结果是什么意义。然后处理好旋转连边。

(发现没有,直线型为什么不能转?因为这样会同时转2个点!网络流没办法处理这种旋转(除非你大力讨论))

以上是关于「清华集训 2017」无限之环的主要内容,如果未能解决你的问题,请参考以下文章

2017.11.26清华集训2017模拟

[LOJ#2328]「清华集训 2017」避难所

[LOJ#2331]「清华集训 2017」某位歌姬的故事

[LOJ#2327]「清华集训 2017」福若格斯

[清华集训2017] 生成树计数

[LOJ#2325]「清华集训 2017」小Y和恐怖的奴隶主