洛谷P1896(状压DP)

该博客介绍了一种使用动态规划方法解决棋盘上放置国王的问题,确保每个国王之间至少间隔一个空格。文章详细阐述了状态定义、关键数据结构、初始化过程以及递推公式,并给出了C++实现代码。核心思路是通过位运算判断国王的合法位置,并计算所有可能的摆放方案总数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

直接说思路:

  • 先解释一下什么叫“状态”:状态也就是每一行若干个国王的摆放方案,比如棋盘为3×3,我们在第1行的第1个格放国王,第二个格不放,第三个格放,那么这一行的状态为101(即放了国王就记为1,不放国王就记为0),这种状态满足国王之间隔着至少一个空格,是合法的,所以我们称之为合法状态,这个状态转成十进制就是5;如果是第1个格和第2个格都放了1,第3个格不放,那么这一行的状态为110,两个国王挨着了,所以是不合法状态,这个状态转成十进制就是6

  • 用到的关键数据结构:

    • state数组:存每一行合法的状态,其中下标为这个状态的编号。比如state[2]=1,就表示第2个合法状态为1,转化成二进制就是001,也就是第一个格、第二个格都没放国王,第3个格子才摆放了1个国王
    • king数组:存对应状态下国王的个数,比如state[2]=1,那么king[2]=1,即第2种合法状态下,只摆了1位国王
    • ans:这个变量的值是每一行中总的合法状态的个数
  • 思路讲解

    • dp[i][j][l]:表示前i行一共用了l位国王,并且第i行国王的摆放状态是第j种合法状态下的方案总数
    • 初始化init():初始化state数组,king数组,计算合法状态数ans
    • 先解决第一行的摆放情况
    • 然后从第二行开始,用一套二层循环,两层循环都是循环state数组,其中外层循环是当前行的摆放状态,内层循环当作上一行的摆放状态,通过位运算更新当前行的dp数组
    • 最后遍历所有方案,输出所有的dp[n][j][k]的和(k是输入的国王总数)
    • 注意,位运算一定一定要加括号,不然很容易出错,因为位运算符的优先级都很低(包括<<, >>, |, ^, &)

还有一点是,每一行有多少个合法状态,涉及到不含连续1的非负整数,本题n最大为9,最多有89种合法状态,所以state数组和king数组开到100就可以

代码如下:

#include <bits/stdc++.h>
#define maxstate 100
using namespace std;
#define ll long long

//state[]存的是每一行可行的状态
//king[]存的是对应状态中可以存放的国王个数
ll state[maxstate],king[maxstate];
ll sum;
int ans;

int n,k;
ll dp[10][100][100];

//计算x的二进制有多少个1
int countOne(int x){
    int cnt=0;
    while (x){
        if(x%2)
            cnt++;
        x>>=1;
    }
    return cnt;
}

void init(){
    int tot=(1<<n)-1;
    for(int i=0;i<=tot;i++){
        if(!((i<<1)&i)){//这一行是用来判断i本身有没有相邻的1
            state[++ans]=i;
            king[ans]=countOne(i);
        }
    }
}

int main(){
    scanf("%d%d",&n,&k);
    init();
    //先处理第一行
    for(int i=1;i<=ans;i++){
        if(king[i]>k)
            continue;
        dp[1][i][king[i]]=1;
    }

    //然后从第二行开始处理
    for(int i=2;i<=n;i++){
        //j是当前行的状态
        for(int j=1;j<=ans;j++){
            //p是上一行的状态
            for(int p=1;p<=ans;p++){
                if(state[p]&state[j])
                    continue;
                if((state[p]<<1)&state[j] )
                    continue;
                if(state[p]&(state[j]<<1))
                    continue;
                for(int s=0;s<=k;s++){
                    if(king[j]+s>k)
                        break;
                    //当前行的状态个数直接是上一行的合法状态的加和
                    dp[i][j][king[j]+s]+=dp[i-1][p][s];
                }
            }
        }
    }

    //遍历整个dp[n][],把所有dp[n][][k]加起来,也就是用光所有国王的方案的个数加起来
    for(int i=1;i<=ans;i++)
        sum+=dp[n][i][k];

    printf("%lld",sum);
}

参考博客:
1.https://www.luogu.com.cn/blog/user16213/solution-p1896
2.https://www.luogu.com.cn/blog/da-ju-ruo-pb/solution-p1896

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值