【uoj336】【清华集训2017】无限之环

题目

描述

​ 给出一个\(n*m\)的网格,每个格子里的水管可能向四个方向都有接口;

​ 游戏的目的是不能让水管漏水,即所有接口都有另一个接口与之相接;

​ 你一步可以将一个格子中的水管旋转\(90^ {\circ}\),规定直线型的接口不能旋转;

​ 求最小的步数;

范围

\(nm \le 2000\)

题解

  • 对每个点拆成上右下左四个接口(令标号为0123),将格子黑白染色并将接口分别向源汇连边;

  • 对每种形状的水管分类讨论,用 \((a,b,w)\) 表示在内部连边,仅讨论黑格子,白格子类似:

    • 0 型:(0,1,1)(0,3,1)(0,2,2)
    • 01型:(0,2,1)(1,3,1)
    • 012型:(0,3,1)(2,3,1)(1,3,2)
    • 其他型:内部不需要连边
  • 主要思想是讨论每种形状转来转去时候的代价;

    #include<bits/stdc++.h>
    #define I(i,j,k) (k*n*m+(i-1)*m+j)
    #define inf 0x3f3f3f3f
    using namespace std; 
    const int N=40010;
    int n,m,o,hd[N],S,T,que[N],head,tail,vis[N],dis[N];
    struct Edge{int v,nt,f,w;}E[N<<2];
    void adde(int u,int v,int c,int w){
      E[o]=(Edge){v,hd[u],c,w};hd[u]=o++;
      E[o]=(Edge){u,hd[v],0,-w};hd[v]=o++;
    }
    void add(int i,int j,int k1,int k2,int w){
      if((i+j)&1)adde(I(i,j,k1),I(i,j,k2),1,w);
      else adde(I(i,j,k2),I(i,j,k1),1,w);
    }
    bool spfa(){
      for(int i=S;i<=T;++i)vis[i]=0,dis[i]=inf;
      head=0;tail=1;vis[que[0]=T]=1;dis[T]=0;
      while(head!=tail){
          int u=que[head++];if(head==T)head=0;
          vis[u]=0;
          for(int i=hd[u];~i;i=E[i].nt)if(E[i^1].f){
              int v=E[i].v;
              if(dis[v]>dis[u]+E[i^1].w){
                  dis[v]=dis[u]+E[i^1].w;
                  if(!vis[v]){
                      vis[v]=1,que[tail++]=v;
                      if(tail==T)tail=0;
                  }
              }
          }
      }
      return dis[S]!=inf;
    }
    int dfs(int u,int F){
      vis[u]=1;
      if(u==T||!F)return F;
      int flow=0,f;
      for(int i=hd[u];~i;i=E[i].nt){
          int v=E[i].v;
          if(!vis[v]&&dis[u]==dis[v]+E[i].w&&E[i].f&&(f=dfs(v,min(E[i].f,F)))){
              flow+=f;F-=f;
              E[i].f-=f;E[i^1].f+=f;
              if(!F)break;
          }
      }
      return flow;
    }
    int main(){
    //    freopen("infinityloop.in","r",stdin);
    //    freopen("infinityloop.out","w",stdout);
      scanf("%d%d",&n,&m);
      S=0;T=n*m*4+1;
      for(int i=S;i<=T;++i)hd[i]=-1;
      int mxf=0;
      for(int i=1;i<=n;++i)
      for(int j=1;j<=m;++j)if((i+j)&1){
          if(i!=1)adde(I(i,j,0),I(i-1,j,2),1,0);
          if(j!=1)adde(I(i,j,3),I(i,j-1,1),1,0);
          if(i!=n)adde(I(i,j,2),I(i+1,j,0),1,0);
          if(j!=m)adde(I(i,j,1),I(i,j+1,3),1,0);
      }
      int mx1=0,mx2=0;
      for(int i=1;i<=n;++i)
      for(int j=1;j<=m;++j){
          int x;scanf("%d",&x);
          if((i+j)&1)mx1+=__builtin_popcount(x);
          else mx2+=__builtin_popcount(x);
          for(int k=0;k<4;++k)if(x>>k&1){
              if((i+j)&1)adde(S,I(i,j,k),1,0);
              else adde(I(i,j,k),T,1,0);
          }
          switch (x){
              case 1:add(i,j,0,1,1);add(i,j,0,3,1);add(i,j,0,2,2);break;
              case 2:add(i,j,1,0,1);add(i,j,1,2,1);add(i,j,1,3,2);break;
              case 4:add(i,j,2,1,1);add(i,j,2,3,1);add(i,j,2,0,2);break;
              case 8:add(i,j,3,0,1);add(i,j,3,2,1);add(i,j,3,1,2);break;
    
              case 3:add(i,j,0,2,1);add(i,j,1,3,1);break;
              case 6:add(i,j,1,3,1);add(i,j,2,0,1);break;
              case 9:add(i,j,0,2,1);add(i,j,3,1,1);break;
              case 12:add(i,j,2,0,1);add(i,j,3,1,1);break;
    
              case 7:add(i,j,0,3,1);add(i,j,2,3,1);add(i,j,1,3,2);break;
              case 11:add(i,j,1,2,1);add(i,j,3,2,1);add(i,j,0,2,2);break;
              case 13:add(i,j,0,1,1);add(i,j,2,1,1);add(i,j,3,1,2);break;
              case 14:add(i,j,1,0,1);add(i,j,3,0,1);add(i,j,2,0,2);break;
    
              //case 0:break;
              //case 5:break;
              //case 10:break;
              //case 15:break;
          }
      }
      if(mx1!=mx2){puts("-1");return 0;}
      int flow=0,cost=0,f; 
      while(spfa()){
          do{
              for(int i=S;i<=T;++i)vis[i]=0;
              f=dfs(S,inf);
              flow+=f;
              cost+=f*dis[S]; 
          }while(vis[T]);
      }
      if(flow!=mx1)puts("-1");
      else printf("%d\n",cost);
      return 0;
    }

转载于:https://www.cnblogs.com/Paul-Guderian/p/10680082.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值