• 棋盘式状压DP模型总结


    导语

      状压DP分两大类,一类是集合式,另一类就是棋盘式(即基于连通性)。

      其中,集合式状压DP难度相较后者略大,形式复杂多变。而棋盘式状压DP的题目样式都相差不多,解法也都殊途同归,因此将这一类题进行总结,不难归结出一套解题模板。

    正文

      我们先看以下三个例题。

    国王

    题目链接

    算法概述

      状态表示:f[i,j,s]表示已经在前i行放了j个国王,且第i行的状态为s的方案数。

      状态转移:考虑第i-1行所有合法状态x,则f[i,j,s]=∑f[i-1,j-count(s),x],其中count(s)表示s中国王的个数。

      算法流程:

      1.考虑将一行的状态进行压缩,然后预处理所有合法状态。

      2.枚举所有合法状态,对于每一种合法状态,处理该状态可由哪些状态转移过来。

      3.循环枚举DP状态,进行状态转移。

    参考代码

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    
    using namespace std;
    typedef long long ll;
    const int N=12;
    
    int stt[1<<N];
    int cnt[1<<N];
    int pre[1<<N][1<<N];
    int sum[1<<N];
    ll f[N][N*N][1<<N];
    int n,m,cnt_stt;
    
    bool check(int state)
    {
        for(int i=0;i<n-1;i++)
            if(state>>i&1 && state>>i+1&1)return false;
        return true;
    }
    
    int count(int state)
    {
        int ans=0;
        for(int i=0;i<n;i++)
            if(state>>i&1)ans++;
        return ans;
    }
    
    int main()
    {
        scanf("%d%d",&n,&m);
        
        for(int i=0;i<(1<<n);i++)if(check(i))stt[++cnt_stt]=i,cnt[i]=count(i);
        
        for(int i=1;i<=cnt_stt;i++)
            for(int j=1;j<=cnt_stt;j++)
                if((stt[i]&stt[j])==0 && check(stt[i]|stt[j]))pre[stt[i]][++sum[stt[i]]]=stt[j];
        
        f[0][0][0]=1;
        for(int i=1;i<=n+1;i++)
            for(int j=0;j<=m;j++)
                for(int s=1;s<=cnt_stt;s++)
                {
                    int a=stt[s];
                    if(j<cnt[a])continue;
                    for(int x=1;x<=sum[a];x++)
                    {
                        int b=pre[a][x];
                        f[i][j][a]+=f[i-1][j-cnt[a]][b];
                    }
                }
        
        printf("%lld
    ",f[n+1][m][0]);
        
        return 0;
    }

    玉米田

    题目链接

    算法概述

      状态表示:f[i,a]表示已经种了前i行,且第i行的状态为a的方案数。

      状态转移:考虑第i-1行的所有合法状态b,则f[i,a]=∑f[i-1,b]。

      算法流程:

      1.预处理所有合法状态。

      2.预处理对于每一个合法状态,能够转移到其的所有合法状态的集合。

      3.循环状态表示,考虑当前行状态是否与地图限制相矛盾,然后循环状态转移。

    参考代码

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    const int N=14,M=1<<12,mod=1e8;
    
    int stt[1<<N];
    int cnt[1<<N];
    int pre[M][M];
    int f[N][1<<N];
    int g[N];
    int n,m,cnt_stt;
    
    bool check(int state)
    {
        for(int i=0;i<m-1;i++)
            if(state>>i&1 && state>>i+1&1)return false;
        return true;
    }
    
    int main()
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
            for(int j=0;j<m;j++)
            {
                int x;scanf("%d",&x);
                g[i]+=!x<<j;
            }
        
        for(int i=0;i<(1<<m);i++)if(check(i))stt[++cnt_stt]=i;
        
        for(int i=1;i<=cnt_stt;i++)
            for(int j=1;j<=cnt_stt;j++)
                if((stt[i]&stt[j])==0)pre[stt[i]][++cnt[stt[i]]]=stt[j];
                
        f[0][0]=1;
        for(int i=1;i<=n+1;i++)
            for(int j=1;j<=cnt_stt;j++)
            {
                int a=stt[j];
                if(g[i]&a)continue;
                for(int s=1;s<=cnt[a];s++)
                {
                    int b=pre[a][s];
                    (f[i][a]+=f[i-1][b])%=mod;
                }
            }
        
        printf("%d
    ",f[n+1][0]);
        
        return 0;
    }

    炮兵阵地

    题目链接

    算法概述

      状态表示:f[i,a,b]表示已经放了前i行,且第i-1行状态为a,第i行状态为b的所有方案中的最大值。

      状态转移:考虑第i-2行所有合法状态c,则f[i,a,b]=min{f[i-1,c,a]+count(b)},其中count(b)表示状态b中放置的个数。

      算法流程:

      1.预处理所有合法状态。

      2.枚举DP状态,考虑当前状态与地图限制是否矛盾,然后枚举DP转移,考虑状态之间的限制。

      考虑到空间限制,需要用滚动数组优化。

    参考代码

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    const int N=110,M=11;
    
    int stt[1<<M];
    int cnt[1<<M];
    int f[2][1<<M][1<<M];
    int g[N];
    int n,m,cnt_stt;
    
    bool check(int state)
    {
        for(int i=0;i<=m-2;i++)
            if((state>>i&1) && (state>>i+1&1 || state>>i+2&1))return false;
        return true;
    }
    
    int count(int state)
    {
        int ans=0;
        for(int i=0;i<m;i++)ans+=state>>i&1;
        return ans;
    }
    
    int main()
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
        {
            static char ch[M];
            scanf("%s",ch);
            for(int j=0;j<m;j++)
                if(ch[j]=='H')g[i]+=1<<j;
        }
    
        for(int i=0;i<(1<<m);i++)
            if(check(i))stt[++cnt_stt]=i,cnt[i]=count(i);
    
        for(int i=1;i<=n+2;i++)
            for(int j=1;j<=cnt_stt;j++)
                for(int k=1;k<=cnt_stt;k++)
                {
                    int a=stt[j],b=stt[k];
                    if(g[i-1]&a|g[i]&b)continue;
                    for(int x=1;x<=cnt_stt;x++)
                    {
                        int c=stt[x];
                        if((a&b)|(a&c)|(b&c))continue;
                        f[i&1][a][b]=max(f[i&1][a][b],f[i-1&1][c][a]+cnt[b]);
                    }
                }
        
        printf("%d
    ",f[n+2&1][0][0]);
        
        return 0;
    }
    

    模型总结

    常规步骤:

      1.预处理所有合法状态。

      2.若空间允许,则对于每一个合法状态,预处理能够转移到其的所有合法状态的集合。

      3.循环DP状态,考虑地图本身的限制,若无矛盾,则循环DP转移。

  • 相关阅读:
    C#接口入门学习
    消息队列接收时报错:对消息队列系统的访问被拒绝
    给某做测试的好友的建议
    在不同的Sql Server 数据库服务器(不同机器)导数据。
    如何让开发人员更好测试?
    存储过程初探
    语音报警.NET开发初探
    vs2010下Siverlight开发环境安装
    C# HttpWebRequest 从google服务器获取google的PageRank PR值
    创建进程API CreateProcess Demo
  • 原文地址:https://www.cnblogs.com/ninedream/p/13404965.html
Copyright © 2020-2023  润新知