【知识点】插头dp

简介:

解决一类网格图上与连通性有关的二维状压dp问题。

例题1:

给定一个$n imes m$的网格图,有些格子不能铺线,其他格子可以。

求铺成若干个互不相交的闭合回路(哈密顿回路)的方案数。

$n,mleq 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
",f[n][m][0]);
    }
    return 0;
}
插头dp1

例题2:

同上题,求只形成一个哈密顿回路的方案数。

$n,mleq 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
",ans);
    return 0;
}
插头dp2
原文地址:https://www.cnblogs.com/YSFAC/p/13335244.html