• 棋盘 || 状压DP


    题意:有一个n*m的棋盘(n,m≤80,n*m≤80)要在棋盘上放k(k≤20)个棋子,使得任意两个棋子不相邻(每个棋子最多和周围4个棋子相邻)。求合法的方案总数。

    思路:对于每一行,如果把没有棋子的地方记为0,有棋子的地方记为1,那么每一行的状态都可以表示成一个2进制数,进而将其转化成10进制。

         那么这个问题的状态转移方程就变成了:

         设dp[i][j][k]表示当前到达第i行,一共使用了j个棋子,且当前行的状态在压缩之后的十进制数为k时的状态总数。那么我们也可以类似的写出状态转移方程:

       dp[i][i][k]=sum(dp[i-1][j-num(k)][w])   num(k)表示 k状态中棋子的个数,w表示前一行的状态。

            最基本的做法是:首先判断k状态是否合法,也就是判断在这一行中是否有2个旗子相邻,然后枚举上一行的状态w,判断w状态是否合法,

               然后判断k状态和w状态上下之间是否有相邻的棋子。

     

    下面是其他网上来的代码,三维状压DP

    https://www.cnblogs.com/a-clown/p/6145462.html

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<string>
    #include<cmath>
    #include<algorithm>
    using namespace std;
    #define ll long long
    using namespace std;
    
    ll dp[82][22][1<<9];///dp[i][j][x]第i行放了j个棋子当前状态为x时的方法数
    ll mark[1<<9];///十进制标记每一行的状态
    ll ans,len;
    
    ll num(ll x) ///记录状态x中1的个数
    {
        ll sum=0;
        while(x)
        {
            if(x&1)sum++;
            x=x>>1;
        }
        return sum;
    }
    
    bool judge(ll x) ///判断状态x是否有相邻的棋子放在一起
    {
        if(x&(x<<1)) return false;
        return true;
    }
    
    int main()
    {
        //freopen("in.txt","r",stdin);
        int n,m,k;
        while(scanf("%d%d%d",&n,&m,&k)!=EOF)
        {
            memset(dp,0,sizeof(dp));
            memset(mark,0,sizeof(mark));
            len=0,ans=0;
            if(m>n) swap(n,m);
    
            for(ll i=0; i<(1<<m); i++) ///初始化第一行的放置方法数//剔除不合法状态(所谓的预处理)
            {
                if(judge(i)) ///若i状态没有相邻的棋子放在一起
                {
                    dp[1][num(i)][len]=1;///则第一行状态为len(i)时1的个数为num(i)时的方法数
                    mark[len++]=i;///标记状态
                }
            }
    
            for(ll i=2; i<=n; i++) ///第二行到第n行
            {
                for(ll j=0; j<=k; j++) ///对于放0***n个棋子
                {
                    for(ll x=0; x<len; x++) ///对于0***len-1个状态(第i行)//枚举
                    {
                        for(ll y=0; y<len; y++)///对于0***len-1个状态(第i-1行)//枚举
                        {
                            ll tmp=num(mark[x]);///第i行状态x中1的个数
                            if(((mark[x]&mark[y])==0) && j>=tmp) ///若上下2行没相邻的且当前的棋子数目大于此行当前状态所用的棋子
                                dp[i][j][x]+=dp[i-1][j-tmp][y];///放法数可相加
                            ///到当前行共用了j个棋子,当前行用了tmp个棋子,状态为x,到上一行共用了j-tmp个棋子,状态为y
                        }
                    }
                }
            }
            for(ll i=0; i<len; i++) ///枚举状态相加
                ans+=dp[n][k][i];
            printf("%lld
    ",ans);
        }
        return 0;
    }

    还有另外的代码:

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    #define ll long long
    const int maxn=(1<<9)+5;
    const int INF=0x3f3f3f3f;
    
    ll dp[85][25][maxn];  ///第i行用j个棋子的k状态能否达到
    int mark[maxn];
    
    int judge(int x)
    {
        if(x&(x<<1)) return 0;
        return 1;
    }
    
    int num(int x)
    {
        int sum=0;
        while(x)
        {
            if(x&1) sum++;
            x=(x>>1);
        }
        return sum;
    }
    
    int main()
    {
        //freopen("in.txt","r",stdin);
        int n,m,k;
        while(scanf("%d%d%d",&n,&m,&k)==3)
        {
            memset(dp,0,sizeof(dp));
            memset(mark,0,sizeof(mark));
            int len=0;
            if(m>n) swap(n,m);
            for(int i=0; i<(1<<m); i++)
            {
                if(judge(i))
                {
                    dp[1][num(i)][len]=1;
                    mark[len++]=i;
                }
            }
    
            for(int i=2; i<=n; i++)
            for(int j=0; j<=k; j++)
            for(int x=0; x<len; x++) ///当前行
            for(int y=0; y<len; y++) ///当前行的前一行
                if(((mark[x]&mark[y])==0) && j>=num(mark[x]) )
                    dp[i][j][x]+=dp[i-1][j-num(mark[x])][y];
    
            ll ans=0;
            for(int i=0; i<len; i++)
                ans+=dp[n][k][i];
            printf("%lld
    ",ans);
        }
        return 0;
    }
  • 相关阅读:
    SharePreferences
    Android实现电话录音功能
    Android短信监听实现,及Android4.4之后短信机制变更
    java指纹识别+谷歌图片识别技术_源代码
    找出相似的图片--C#
    vim常用命令整理
    机器学习实战-logistic回归分类
    机器学习实战-朴素贝叶斯垃圾邮件分类
    机器学习实战-随机森林二分类问题
    分类算法
  • 原文地址:https://www.cnblogs.com/ohuo/p/12274497.html
Copyright © 2020-2023  润新知