插头DP笔记
仍然是板子博。
插头DP,即连通性状态压缩DP。一般用来解决棋盘上的回路计数。别的没见过
与一般的状压不同,插头DP采用轮廓线DP,记录转移轮廓线上的插头(可以形象地理解为回路与轮廓线的交点),逐格而不是逐行进行转移。相对地,转移复杂度变为了 \(O(1)\) 。
网格上的回路问题由于存在一定特殊性(插头一一配对且不相交),记录状态时可以用括号表示法,简化代码也降低了复杂度。因为左右括号本质不同,要采用三进制状态,为了方便一般用四进制。
不同情况的转移不尽相同。这里讨论洛谷模板,求带障碍棋盘上的哈密顿回路数。
大力分类讨论。这里画图更易理解。但因为这片博本质是板子,就不画了。可以看这篇博客
讨论当前转移各自上插头的状态:右插头和下插头的位置,有无插头,左括号还是右括号。根据状态进行转移。
状态四进制位从左到右对应轮廓线上从左到右的插头,第 \(j\) 个格子的右,下插头分别为 bit>>(j-1<<1)&3
和 bit>>(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;
}