题307.状压dp-acwing-Q1064--小国王


题307.状压dp-acwing-Q1064–小国王


一、题目

在这里插入图片描述

二、题解

用dp五步法分析该题:
1.确定dp数组,明确其含义。想着说用dp[i][j]表示放到了第i行,且已经使用了j个棋子时对应的方案数,但是其中的状态过于复杂,难以计算,于是将状态进一步分解,多开一个维度表示状态,则可用dp[i][j][a]表示放到第i行,共用了j个棋子,且最后一行对应的状态为下标为a的合法状态下对应的方案数。
2.确定递推公式。采用y式dp分析法如下:

在这里插入图片描述

则递推公式如下:dp[i][j][a]+=dp[i-1][j-count(a)][b];//a由b状态转移过来,b处于i-1行,且那个时候用了j-num个棋子
在这里插入图片描述
3.初始化dp数组。dp[0][0][0]=1;//显然放到第0行,放了0个棋子,状态为下标0对应的合法状态(其实就是0)方案数为1。其余为0
4.确定遍历顺序。i从前往后遍历行,j从前往后遍历棋子个数,a,b遍历状态
5.打印dp数组。
代码及详细注释如下:

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;//防止数据溢出
const int maxn=12,maxm=1<<10,maxk=110;
//n最大其实是10,但是后期要用到11行,所以maxn就整到了12,m表示状态个数,显然由于1行是10列,所以状态个数最多有2^10个,maxk为最大棋数

int N,K;
vector<int> state;//存储所有合法状态,对应棋盘上的一行,相邻之间不能同时为1
vector<int> head[maxm];//head[i]存储i下标对应的合法状态的上一个合法状态对应的下标
int cnt[maxm];//cnt[i]表示表示下标为i的合法状态所含的1的个数,即棋子的个数
ll dp[maxn][maxk][maxm];//dp[i][j][a]表示放到第i行,共用了j个棋子,且最后一行对应的状态为下标为a的合法状态下对应的方案数

int check(int state)//检查状态state每相邻两个数是否同时为1
{
    for(int i=0;i<N-1;i++)//最多移动N-1位(i+1=N-1)
    {
        //右移i位直接将state这个数表示的二进制状态中第i位上的数移动到了最右边,与上1后如果等于1则第i位上数为1否则为0
        if((state>>i&1)&&(state>>i+1&1))//循环直接看第i位和第i+1位
        {
            return 0;
        }
    }
    return 1;
}

int count(int state)//统计该状态对应的1的个数,即棋子的个数
{
    int res=0;
    for(int i=0;i<N;i++)
    {
        res+=state>>i&1;
    }
    return res;
}

int main()
{
    cin>>N>>K;
    //1.预处理
    for(int i=0;i<1<<N;i++)//枚举到2^N
    {
        if(check(i))//判断状态是否合法
        {
            state.push_back(i);//存入合法状态
            cnt[i]=count(i);//记录该合法状态1的个数
        }
    }
    for(int i=0;i<state.size();i++)
    {
        for(int j=0;j<state.size();j++)//i,j遍历每个合法状态
        {
            int a=state[i],b=state[j];
            if((a&b)==0&&check(a|b))//看b到a(上->下)是否可达,两种状态对应的二进制数每一位对应数不能同时为1,且相邻位不能同时为1
            {
                head[i].push_back(j);//j->i
            }
        }
    }
    //2.dp
    dp[0][0][0]=1;//显然放到第0行,放了0个棋子,状态为下标0对应的合法状态(其实就是0)方案数为1
    for(int i=1;i<=N+1;i++)//i遍历行数,有意遍历到N+1,使得答案能够被存入N+1行
    {
        for(int j=0;j<=K;j++)//j遍历棋子个数
        {
            for(int a=0;a<state.size();a++)//a遍历该行状态
            {
                for(int b:head[a])//b为该状态对应的上一层状态
                {
                    int num=cnt[state[a]];//该行状态对应的1的个数
                    if(j>=num)//这1的个数当然不能超过棋子的个数
                    {
                        dp[i][j][a]+=dp[i-1][j-num][b];//a由b状态转移过来,b处于i-1行,且那个时候用了j-num个棋子
                    }
                }
            }
        }
    }
    cout<<dp[N+1][K][0];//N+1行状态为什么棋子也不放,且那个时候共使用了K个棋子,对应的方案数就是答案
}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值