• 【知识点】插头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
  • 相关阅读:
    数据库表中批量替换某个字段的方法
    css清除浮动方法大全
    IE6中的常见BUG与相应的解决办法
    [转]Oracle 12c多租户特性详解:PDB 的创建、克隆与维护
    Oracle 数据库导入导出 dmp文件
    Tomcat 服务应用
    Perforce 与Source Insight, Visual Studio集成
    Oracle 11g必须开启的服务及服务详细介绍
    Python学习笔记8-单元测试(1)
    Python学习笔记7-高级迭代器
  • 原文地址:https://www.cnblogs.com/YSFAC/p/13335244.html
Copyright © 2020-2023  润新知