插头DP笔记

仍然是板子博。

插头DP,即连通性状态压缩DP。一般用来解决棋盘上的回路计数。别的没见过

与一般的状压不同,插头DP采用轮廓线DP,记录转移轮廓线上的插头(可以形象地理解为回路与轮廓线的交点),逐格而不是逐行进行转移。相对地,转移复杂度变为了 \(O(1)\)

网格上的回路问题由于存在一定特殊性(插头一一配对且不相交),记录状态时可以用括号表示法,简化代码也降低了复杂度。因为左右括号本质不同,要采用三进制状态,为了方便一般用四进制。

不同情况的转移不尽相同。这里讨论洛谷模板,求带障碍棋盘上的哈密顿回路数。

大力分类讨论。这里画图更易理解。但因为这片博本质是板子,就不画了。可以看这篇博客

讨论当前转移各自上插头的状态:右插头和下插头的位置,有无插头,左括号还是右括号。根据状态进行转移。

状态四进制位从左到右对应轮廓线上从左到右的插头,第 \(j\) 个格子的右,下插头分别为 bit>>(j-1<<1)&3bit>>(j<<1)&3 。为了方便,可以预处理 inc 数组, inc[i]=inc[i-1]<<2 ,可以快速提取插头在状态内的位置进行状态修改。

为了节省内存,采用滚动数组和哈希表。

小清新压行代码
#include<bits/stdc++.h>
using namespace std;

namespace IO{
    #define int LL
    typedef double DB;
    typedef long long LL;
    LL read(){
        LL x=0,f=0; char ch=getchar();
        while(ch>'9'||ch<'0'){ f|=(ch=='-'); ch=getchar(); }
        while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
        return f?-x:x;
    } char output[50];
    void write(LL x,char sp){
        int len=0;
        if(x<0) putchar('-'), x=-x;
        do{ output[len++]=x%10+'0'; x/=10; }while(x);
        for(int i=len-1;~i;i--) putchar(output[i]); putchar(sp);
    }
    void ckmax(int& x,int y){ x=x>y?x:y; }
    void ckmin(int& x,int y){ x=x<y?x:y; }
} using namespace IO;

const int NN=16;
int n,m,edx,edy,ans,mp[NN][NN];
char c[NN];

namespace PlugDP{
    const int MM=2<<NN,mod=299987;
    int pre,now,cnt[2],inc[NN],nex[MM],head[mod+5],sta[2][MM],val[2][MM];
    void prework(){ for(int i=(inc[0]=1);i<=15;i++) inc[i]=inc[i-1]<<2; }
    int roll(){ for(int i=1;i<=cnt[now];i++) sta[now][i]<<=2; return 1; }
    int swth(){ pre=now; now^=1; cnt[now]=0; memset(head,0,sizeof(head)); return 1; }
    void insert(int st,int va){
        int u=st%mod+1;
        for(int i=head[u];i;i=nex[i])
            if(!(sta[now][i]^st)) return val[now][i]+=va,void();
        nex[++cnt[now]]=head[u]; head[u]=cnt[now];
        sta[now][cnt[now]]=st; val[now][cnt[now]]=va;
    }
    void dprogram(){
        prework(); cnt[now]=1; val[now][1]=1; sta[now][1]=0;
        for(int i=1;i<=n;i++) for(int j=roll();j<=m;j++)
            for(int k=swth();k<=cnt[pre];k++){
                int bit=sta[pre][k],num=val[pre][k];
                int b1=(bit>>(j-1<<1))&3,b2=(bit>>(j<<1))&3;
                if(!mp[i][j]){ if(!b1&&!b2) insert(bit,num); }
                else if(!b1&&!b2){ if(mp[i+1][j]&&mp[i][j+1]) insert(bit+inc[j-1]+inc[j]*2,num); }
                else if(!b1&&b2){
                    if(mp[i][j+1]) insert(bit,num);
                    if(mp[i+1][j]) insert(bit-inc[j]*b2+inc[j-1]*b2,num);
                } else if(b1&&!b2){
                    if(mp[i+1][j]) insert(bit,num);
                    if(mp[i][j+1]) insert(bit-inc[j-1]*b1+inc[j]*b1,num);
                } else if(b1==1&&b2==1){
                    int tmp=1,tbi=bit-inc[j]-inc[j-1];
                    for(int l=j+1;l<=m;l++){
                        if(((bit>>(l<<1))&3)==1) ++tmp;
                        if(((bit>>(l<<1))&3)==2) --tmp;
                        if(!tmp){ insert(tbi-inc[l],num); break; }
                    }
                } else if(b1==2&&b2==2){
                    int tmp=1,tbi=bit-(inc[j]+inc[j-1]<<1);
                    for(int l=j-2;l>=0;l--){
                        if(((bit>>(l<<1))&3)==1) --tmp;
                        if(((bit>>(l<<1))&3)==2) ++tmp;
                        if(!tmp){ insert(tbi+inc[l],num); break; }
                    }
                } else if(b1==2&&b2==1) insert(bit-inc[j-1]*2-inc[j],num);
                else if(i==n&&j==m) ans+=num;
            }
    }
} using namespace PlugDP;

signed main(){
    n=read(); m=read();
    for(int i=1;i<=n;i++){
        scanf("%s",c+1);
        for(int j=1;j<=m;j++){
            mp[i][j]=(c[j]=='.');
            if(mp[i][j]) edx=i,edy=j;
        }
    }
    dprogram();
    write(ans,'\n');
    return 0;
}
posted @ 2022-01-11 20:47  keen_z  阅读(100)  评论(0编辑  收藏  举报