• [整理]插头 dp


    全名是基于连通性状态压缩的动态规划问题,从这个名字可以看出它的特征:是状压 dp(废话)、维护的是连通性。
    怎么理解呢?接下来以洛谷模板题为例讲解它的维护方法。
    考虑如何维护一条轮廓线,我们需要引入插头的概念:一个格子的插头是指它可以连出去的某条边(或者理解为让轮廓线插♂进来的地方)。对于这题我们应从上到下从左到右递推,就应该考虑上一行对下一行有什么影响:需要记录每一行的插头情况和连通情况(不理解没关系,下面有详细图解)。
    具体来说,插头情况就是每个格子有没有插头,连通情况是指每个插头对应的连通轮廓线情况。例如:下图中红线处的插头情况可以状压成 (1111),连通情况是 ((1,2)) 连通、((3,4)) 连通。
    dp1.png
    我们很容易发现,插头之间的连通情况就像括号匹配一样不会出现交叉,由此产生了括号表示法:用左/右括号代表每个连通分量中从左边/右边插出来的插头,空代表没有插头,这样上图的连通情况又可以表示为 ( exttt{(())}),然后我们发现其实插头情况已经在连通情况中表示出来了,所以去掉这一维。
    然后我们就可以大力分类讨论当前格子的左/上的插头情况了:

    1. 当前格子为障碍,当且仅当都没有插头时才能转移;
    2. 当前格子无障碍且都没有插头时,当前格子的右/下插头都应连接;
      dp2.png
    3. 有一个方向有插头时,可以选择直走或拐弯;
      dp3.1.png
      dp3.2.png
    4. 都有插头且括号类型相同时,遍历找到对应括号转移(以都为左括号为例);
      dp4.1.png
    5. 都有插头且左插头为右括号、上插头为左括号时,闭合插头;
      dp5.png
    6. 都有插头且左插头为左括号、上插头为右括号时,当且仅当转移到结尾才能闭合。
      dp6.png

    由于有三种标记,状压时应使用高于 (2) 的进制,为了方便位运算这里采用四进制状压。
    然而我们发现这里的空间复杂度是 (mathcal O(nm4^m)),于是滚动数组。但是时空复杂度依然巨大多,我们发现有很多的状态是没用的,可以不用暴力枚举上一行转移而是用 Hash 表之类的东西存下来可行状态。
    模板题核心常数巨大代码(第一次见优化比本体难写

    const int N=100003;
    int n,m,a[20][20],ex,ey,ans;
    struct Edge {
      int to[2],nxt,wei[2];
    }e[N];
    int hd[N],cnt[2];
    il void ade(int cur,int st,int val){
      int u=st%N;
      for(rg int i=hd[u];i;i=e[i].nxt){
        if(e[i].to[cur]==st)return (void)(e[i].wei[cur]+=val);
      }
      e[++cnt[cur]].to[cur]=st,e[cnt[cur]].wei[cur]=val;
      e[cnt[cur]].nxt=hd[u],hd[u]=cnt[cur];
      return;
    }
    #define D(st,k) (st>>(k<<1)&3)
    #define G(val,k) (val<<(k<<1))
    signed main(){
      Read(n),Read(m);
      for(rg int i=1;i<=n;i++){
        char ipt[20];scanf("%s",ipt+1);
        for(rg int j=1;j<=m;j++){
          if(ipt[j]=='.')a[i][j]=1,ex=i,ey=j;
        }
      }
      int cur=0,lst=1;ade(cur,0,1);
      for(rg int i=1;i<=n;i++){
        for(rg int j=1;j<=cnt[cur];j++)e[j].to[cur]<<=2;
        for(rg int j=1;j<=m;j++){
          lst=cur,cur^=1,cnt[cur]=0,memset(hd,0,sizeof(hd));
          for(rg int c=1;c<=cnt[lst];c++){
            int st=e[c].to[lst],v=e[c].wei[lst];
            int lt=D(st,j-1),up=D(st,j),now;
            if(!a[i][j]){//1
              if(!lt&&!up)ade(cur,st,v);
            }else if(!lt&&!up){//2
              now=st+G(1,j-1)+G(2,j);
              if(a[i+1][j]&&a[i][j+1])ade(cur,now,v);
            }else if(!(lt&&up)){//3
              if(lt){
                if(a[i][j+1])ade(cur,st-G(lt,j-1)+G(lt,j),v);
                if(a[i+1][j])ade(cur,st,v);
              }else {
                if(a[i+1][j])ade(cur,st+G(up,j-1)-G(up,j),v);
                if(a[i][j+1])ade(cur,st,v);
              }
            }else if(lt==up){//4
              now=st-G(lt,j-1)-G(up,j);
              if(lt==1){
                for(rg int k=j+1,tp=1;k<=m;k++){
                  int u=D(st,k);
                  if(u==1)tp++;
                  else if(u==2)tp--;
                  if(!tp){
                    ade(cur,now-G(1,k),v);break;
                  }
                }
              }else {
                for(rg int k=j-2,tp=1;k>0;k--){
                  int u=D(st,k);
                  if(u==2)tp++;
                  else if(u==1)tp--;
                  if(!tp){
                    ade(cur,now+G(1,k),v);break;
                  }
                }
              }
            }else if(lt==2&&up==1){//5
              ade(cur,st-G(lt,j-1)-G(up,j),v);
            }else if(i==ex&&j==ey)ans+=v;//6
          }
        }
      }
      cout<<ans<<endl;
      KafuuChino HotoKokoa
    }
    
    内容来自_ajhfff_的博客(https://www.cnblogs.com/juruoajh/),未经允许,不得转载。
  • 相关阅读:
    RequireJS 模块化加载框架使用
    MySQL数据类型(最大值 和 最小值)
    utf-8的中文是一个字符占几个字节
    spring的事务是什么?与数据库的事务是否一样
    事务和锁机制是什么关系? 开启事务就自动加锁了吗?
    关于数据库主键和外键
    浅谈Mysql共享锁、排他锁、悲观锁、乐观锁及其使用场景
    TCP/IP协议
    分库分表分区需要考虑的问题及方案
    Redis(十一):Redis的事务功能详解
  • 原文地址:https://www.cnblogs.com/juruoajh/p/14932517.html
Copyright © 2020-2023  润新知