有些 状压 \(DP\) 问题要求咱们记录状态的连通性信息,这类问题通常被形象的称为插头 \(DP\) 或连通性状态压缩 \(DP\) 。ios
例如格点图的哈密顿路径计数,求棋盘的黑白染色方案知足相同颜色之间造成一个连通块的方案数,以及特定图的生成树计数等等。学习
这些问题一般须要咱们对状态的连通性进行编码,讨论状态转移过程当中连通性的变化。编码
首先要明确两个概念:3d
轮廓线:已决策状态和未决策状态的分界线。code
插头:一个格子某个方向的插头存在,表示这个格子在这个方向与相邻格子相连。blog
咱们要状压的就是轮廓线上插头的状态。继承
具体来讲,能够把路径的合并看做括号的匹配。get
通常的状压 \(dp\) 只有 \(0,1\) 两个状态,分别表示有和没有,可是这道题须要用三进制的状态压缩,\(0,1,2\) 分别表示没有括号,有左括号,有右括号。string
之因此要分左右括号是由于要区分下面这两种状况:
第一种状况中间的两个括号是不能合并的,由于要刚好造成一条回路,第二种状况则可以合并。
一条轮廓线会由 \(n+1\) 条线段组成,其中 \(n\) 条是左右方向的,另外 \(1\) 条是上下方向的。
为了方便解压状态,咱们用四进制来表示,同时减小枚举的状态,要把全部的状态存到哈希表里。
转移的时候大力分类讨论:
\(1\)、当前的位置不能有路径通过
若是没有向右的插头或者向下的插头,直接继承上一个格子的答案,
不然不存在合法的方案。
if(s[i][j]=='*'){ if(!r && !dow) f[now].ad(nzt,nval); }
\(2\)、当前的位置必须通过而且没有向右的插头或者向下的插头。
须要在当前的格子新开一个向右的插头和向下的插头,而且把向右的插头标记为右括号,把向下的插头标记为左括号。
我在转移状态以前就去判断这个状态是否合法,这样会比较好写。
else if(!r && !dow){ if(s[i][j+1]=='.' && s[i+1][j]=='.') f[now].ad(nzt|2|(1<<j*2),nval); }
\(3\)、当前的位置必须通过而且只有向右的插头或者向下的插头。
能够继续沿着以前的方向或者改变插头的方向,左右括号不变。
else if(r && !dow){ if(s[i][j+1]=='.') f[now].ad(nzt,nval); if(s[i+1][j]=='.') f[now].ad(nzt^r|(r<<j*2),nval); } else if(dow && !r){ if(s[i+1][j]=='.') f[now].ad(nzt,nval); if(s[i][j+1]=='.') f[now].ad(nzt^(dow<<j*2)|dow,nval); }
\(4\)、当前的位置必须通过而且有一个表明左括号的右插头和一个表明右括号的下插头。
若是当前的点是图中右下角的终止节点而且不存在其它匹配的括号更新答案。
else if(r==1 && dow==2){ if(i==edx && j==edy && (nzt^r^(dow<<j*2))==0) ans+=nval; }
\(5\)、当前的位置必须通过而且有一个表明左括号的下插头和一个表明右括号的右插头。
将这两个括号匹配。
else if(r==2 && dow==1){ f[now].ad(nzt^r^(dow<<j*2),nval); }
\(6\)、当前的位置必须通过而且有一个下插头和一个右插头,而且这两个插头都表明左括号。
一直向右找,找到第一个左括号和右括号刚好匹配的位置,把这个位置的右括号改成左括号,以前的两个左括号直接匹配。
else if(r==1 && dow==1){ cs1=nzt^r^(dow<<j*2); for(rg int o=j+1,p=1;o<=m;o++){ cs2=cs1>>o*2&3; p+=(cs2==1)-(cs2==2); if(!p){ cs1^=3<<o*2; break; } } f[now].ad(cs1,nval); }
\(7\)、当前的位置必须通过而且有一个下插头和一个右插头,而且这两个插头都表明右括号。
和上面的状况同样,可是须要改为向左找。
else if(r==2 && dow==2){ cs1=nzt^r^(dow<<j*2); for(rg int o=j-1,p=1;o;o--){ cs2=cs1>>o*2&3; p+=(cs2==2)-(cs2==1); if(!p){ cs1^=3<<o*2; break; } } f[now].ad(cs1,nval); }
#include<cstdio> #include<iostream> #include<cstring> #include<cmath> #include<algorithm> #define rg register const int maxn=14,mod=1e5+3,maxm=1e5+5; int n,m,edx,edy; char s[maxn][maxn]; long long ans=0; struct has{ struct asd{ int nxt,zt; long long val; }b[maxm]; has(){ memset(h,-1,sizeof(h)); tot=1; } int tot,h[maxm]; void cls(){ memset(h,-1,sizeof(h)); tot=1; } void ad(rg int zt,rg long long val){ rg int now=zt%mod; for(rg int i=h[now];i!=-1;i=b[i].nxt){ if(b[i].zt==zt){ b[i].val+=val; return; } } b[tot].val=val; b[tot].zt=zt; b[tot].nxt=h[now]; h[now]=tot++; } }f[2]; int main(){ scanf("%d%d",&n,&m); for(rg int i=1;i<=n;i++){ scanf("%s",s[i]+1); for(rg int j=1;j<=m;j++){ if(s[i][j]=='.') edx=i,edy=j; } } rg int now=0,nzt,r,dow,cs1,cs2; rg long long nval; f[0].ad(0,1); for(rg int i=1;i<=n;i++){ for(rg int j=1;j<=m;j++){ now^=1; f[now].cls(); for(rg int k=1;k<f[now^1].tot;k++){ nzt=f[now^1].b[k].zt,nval=f[now^1].b[k].val; r=nzt&3,dow=nzt>>j*2&3; if(s[i][j]=='*'){ if(!r && !dow) f[now].ad(nzt,nval); } else if(!r && !dow){ if(s[i][j+1]=='.' && s[i+1][j]=='.') f[now].ad(nzt|2|(1<<j*2),nval); } else if(r && !dow){ if(s[i][j+1]=='.') f[now].ad(nzt,nval); if(s[i+1][j]=='.') f[now].ad(nzt^r|(r<<j*2),nval); } else if(dow && !r){ if(s[i+1][j]=='.') f[now].ad(nzt,nval); if(s[i][j+1]=='.') f[now].ad(nzt^(dow<<j*2)|dow,nval); } else if(r==1 && dow==2){ if(i==edx && j==edy && (nzt^r^(dow<<j*2))==0) ans+=nval; } else if(r==2 && dow==1){ f[now].ad(nzt^r^(dow<<j*2),nval); } else if(r==1 && dow==1){ cs1=nzt^r^(dow<<j*2); for(rg int o=j+1,p=1;o<=m;o++){ cs2=cs1>>o*2&3; p+=(cs2==1)-(cs2==2); if(!p){ cs1^=3<<o*2; break; } } f[now].ad(cs1,nval); } else if(r==2 && dow==2){ cs1=nzt^r^(dow<<j*2); for(rg int o=j-1,p=1;o;o--){ cs2=cs1>>o*2&3; p+=(cs2==2)-(cs2==1); if(!p){ cs1^=3<<o*2; break; } } f[now].ad(cs1,nval); } } } } printf("%lld\n",ans); return 0; }