【知识点】插头dp
简介:
解决一类网格图上与连通性有关的二维状压dp问题。
例题1:
给定一个$n\times m$的网格图,有些格子不能铺线,其他格子可以。
求铺成若干个互不相交的闭合回路(哈密顿回路)的方案数。
$n,m\leq 12$。
题解:
一般的网格图状压dp都是整行整行的转移,本质上是一维的,而二维只能逐个格子转移。
我们考虑按先从小到大枚举行,再从小到大枚举列的顺序转移格子。
那么每次转移好的状态就是缺了一个角的矩形,有一条由m条横线和1条竖线组成的轮廓线把已转移与未转移的格子分开。
我们再考虑怎么记录一个状态,显然不用把所有格子都记录下来,我们只需要记录有后效性的变量即可。
(任何动态规划其实都是把有后效性的变量扔到状态里,把无后效性的变量作为转移值)
显然有后效性的变量是“每根轮廓线上有没有插进来的线”,那么我们的每个状态可以用一个m+1位的二进制数表示。
转移的时候相当于把一个格子的左/上轮廓线转移到右/下轮廓线,分类讨论转移即可。
注意每换一次行所有状态需要左移一位,因为那条竖线从最右边移到了最左边,相当于一个0从最高位移到了最低位。
复杂度$O(nm2^{m})$。
代码:
#include<bits/stdc++.h> #define maxn 15 #define maxm 500005 #define inf 0x7fffffff #define ll long long #define rint register int #define debug(x) cerr<<#x<<": "<<x<<endl #define fgx cerr<<"--------------"<<endl #define dgx cerr<<"=============="<<endl using namespace std; int mp[maxn][maxn]; ll f[maxn][maxn][1<<13]; inline int read(){ int x=0,f=1; char c=getchar(); for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f; } int main(){ int T=read(); while(T--){ int n=read(),m=read(); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) mp[i][j]=read(); memset(f,0,sizeof(f)); f[0][m][0]=1; for(int i=1;i<=n;i++){ for(int k=0;k<(1<<m+1);k++) f[i][0][k<<1]=f[i-1][m][k]; for(int j=1;j<=m;j++) for(int k=0;k<(1<<m+1);k++){ int b1=k&(1<<j-1),b2=k&(1<<j); ll val=f[i][j-1][k]; if(!mp[i][j]) f[i][j][k]+=(!b1&&!b2)?val:0; else if(!b1&&!b2) f[i][j][k+(1<<j)+(1<<j-1)]+=(mp[i][j+1]&&mp[i+1][j])?val:0; else if(!b1&&b2){ if(mp[i][j+1]) f[i][j][k]+=val; if(mp[i+1][j]) f[i][j][k-(1<<j)+(1<<j-1)]+=val; } else if(b1&&!b2){ if(mp[i+1][j]) f[i][j][k]+=val; if(mp[i][j+1]) f[i][j][k-(1<<j-1)+(1<<j)]+=val; } else f[i][j][k-(1<<j-1)-(1<<j)]+=val; } } printf("%lld\n",f[n][m][0]); } return 0; }
例题2:
同上题,求只形成一个哈密顿回路的方案数。
$n,m\leq 12$。
题解:
注意到如果你在转移时把两个已经连通的插头连上,那么当前状态就不合法了。
所以我们在记录状态时还需要记录哪些插头是连通的。
可以直接最小表示法(每有一个连通块进制数+1),也可以用括号匹配的方法表示。
为什么能用括号匹配:
- 一个连通块最多只有两个插头,于是可以用一对匹配的左右括号表示一个连通块。
- 如果轮廓线上有四个插头,从左到右分别是a,b,c,d,那么若a,c连通,b,d必定不连通,这和括号序列不能交叉匹配的性质一样。
- 一个确定的括号序列只有一种合法匹配方式,能够与每种状态一一对应。
于是我们每位需要记录有/没有插头,是左/右括号,为了方便采用四进制。
复杂度$O(nm2^{m})$,由于状态太多,需要压空间或哈希。
代码:
#include<bits/stdc++.h> #define maxn 15 #define maxm 300005 #define inf 0x7fffffff #define mod 299989 #define ll long long #define rint register int #define debug(x) cerr<<#x<<": "<<x<<endl #define fgx cerr<<"--------------"<<endl #define dgx cerr<<"=============="<<endl using namespace std; char str[maxn]; ll mp[maxn][maxn],f[2][maxm]; ll now,hd[maxm],nxt[maxm],tot[2],sta[2][maxm]; inline ll read(){ ll x=0,f=1; char c=getchar(); for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f; } inline void ins(ll st,ll val){ ll h=st%mod+1; for(rint i=hd[h];i;i=nxt[i]) if(sta[now][i]==st) {f[now][i]+=val;return;} nxt[++tot[now]]=hd[h],hd[h]=tot[now]; sta[now][tot[now]]=st,f[now][tot[now]]=val; } int main(){ ll n=read(),m=read(),ex=0,ey=0,ans=0; for(rint i=1;i<=n;i++){ scanf("%s",str+1); for(rint j=1;j<=m;j++) if(str[j]=='.') mp[i][j]=1,ex=i,ey=j; } now=0,tot[now]=1,f[now][1]=1,sta[now][1]=0; for(rint i=1;i<=n;i++){ for(rint k=1;k<=tot[now];k++) sta[now][k]<<=2; for(rint j=1;j<=m;j++){ ll las=now; now^=1,tot[now]=0; memset(hd,0,sizeof(hd)); for(rint k=1;k<=tot[las];k++){ ll st=sta[las][k],val=f[las][k]; //cout<<st<<" "<<val<<endl; ll b1=(st>>(j*2-2))%4,b2=(st>>(j*2))%4; if(!mp[i][j]){if(!b1&&!b2)ins(st,val);} else if(!b1&&!b2){if(mp[i][j+1]&&mp[i+1][j])ins(st+(1<<(j*2-2))+(2<<(j*2)),val);} else if(b1&&!b2){ if(mp[i+1][j]) ins(st,val); if(mp[i][j+1]) ins(st-(b1<<(j*2-2))+(b1<<(j*2)),val); } else if(!b1&&b2){ if(mp[i][j+1]) ins(st,val); if(mp[i+1][j]) ins(st-(b2<<(j*2))+(b2<<(j*2-2)),val); } else if(b1==1&&b2==1){ ll num=1; for(rint t=j+1;t<=m;t++){ if((st>>(t*2))%4==1) num++; if((st>>(t*2))%4==2) num--; if(!num){ins(st-(1<<(j*2-2))-(1<<(j*2))-(1<<(t*2)),val);break;} } } else if(b1==2&&b2==2){ ll num=1; for(rint t=j-2;t>=0;t--){ if((st>>(t*2))%4==1) num--; if((st>>(t*2))%4==2) num++; if(!num){ins(st-(2<<(j*2-2))-(2<<(j*2))+(1<<(t*2)),val);break;} } } else if(b1==2&&b2==1) ins(st-(2<<(j*2-2))-(1<<(j*2)),val); else if(b1==1&&b2==2){if(i==ex&&j==ey)ans+=val;} } } } printf("%lld\n",ans); return 0; }