「刷题记录」国王(状压DP)
第一道状压 DP 题 夏令营学长讲了一晚上我也没听明白
题目传送门:国王
分析#
这道题似乎和八皇后很像,但由于国际象棋中规则不同,国王只能吃掉周围 个格的棋子,所以也是有所不同的
我们一步一步来
首先,同一行允许有多个国王,前提是这些国王不紧挨着
其次,同一列允许有多个国王,前提也是这些国王不紧挨着
再然后,就是一个国王的四个角上不能有其他国王,同时国王总数不能超过
现在,我们已经找到规则了
状压部分#
一个位置放国王,在二进制上就是 ,否则就是
二进制数 代表第一个位置放国王,第三个位置放国王,第二和第四个位置不放
我们二进制的放国王顺序是从右往左,因为二进制每次加 都是从最右边开始加的,因此顺序也就设为从右往左
DP 部分#
先设置状态
:第 行,第 种情况,放了 个棋子
:情况 需要多少个棋子
转移方程式:
: 情况下, 情况是合法的
代码#
先预处理出每一种状态
all = (1 << n) - 1; // 每一个格都放的情况
for (int i = 0; i <= all; ++i) { // 枚举所有的情况
int tmp = i;
while (tmp) {
need[i] += (tmp & 1); // 统计这种情况需要多少个国王
tmp >>= 1;
if (need[i] > k)
break;
// 一个小优化,最多放k个国王,如果已经大于k个,则这种情况一定不会选,直接退出即可
}
}
判断情况是否合法,即判断左右是否相邻
for (int i = 0; i <= all; ++i) {
can[i] = !((i << 1) & i); // 判断左右是否紧挨着
}
然后预处理出第一行的合法情况
for (int i = 0; i <= all; ++i) {
if (can[i] && need[i] <= k)
dp[1][i][need[i]] = 1;// 预处理出第一行
}
然后,枚举每一行的每一种合法情况,再枚举上一行的合法情况,判断当前这一行的这种情况对于上一行来说是否合法
for (int i = 2; i <= n; ++i) { // 枚举当前行
for (int j = 0; j <= all; ++j) { // 枚举当前行的每一种情况
if (can[j]) {
for (int h = 0; h <= all; ++h) { // 枚举上一行的每一种情况
if (!can[h])
continue;
if (j & h) // 判断上下是否紧挨着
continue;
if ((j << 1) & h) // 判断当前国王的位置是否是上一行国王位置的右下角
continue;
if ((j >> 1) & h) // 判断当前国王的位置是否是上一行国王位置的左下角
continue;
for (int l = k; l >= need[j]; --l) { // 枚举国王个数
dp[i][j][l] += dp[i - 1][h][l - need[j]];
}
}
}
}
}
完整代码:#
点击查看代码
/*
date: 2022.9.10
worked by yi_fan0305
*/
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
int n, k, all;
ll dp[15][1010][100];
// 一维 行数 二维 状态 三维 国王个数
int need[1010], can[1010];
// need 第i种情况所需的国王个数 can 第i种情况是否合法
inline ll read() {
ll x = 0;
int fg = 0;
char ch = getchar();
while (ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
}
int main() {
n = read();
k = read();
all = (1 << n) - 1; // 每一个格都放的情况
for (int i = 0; i <= all; ++i) { // 枚举所有的情况
int tmp = i;
while (tmp) {
need[i] += (tmp & 1); // 统计这种情况需要多少个国王
tmp >>= 1;
if (need[i] > k)
break;
// 一个小优化,最多放k个国王,如果已经大于k个,则这种情况一定不会选,直接退出即可
}
}
for (int i = 0; i <= all; ++i) {
can[i] = !((i << 1) & i); // 判断左右是否紧挨着
}
for (int i = 0; i <= all; ++i) {
if (can[i] && need[i] <= k)
dp[1][i][need[i]] = 1;// 预处理出第一行
}
for (int i = 2; i <= n; ++i) { // 枚举当前行
for (int j = 0; j <= all; ++j) { // 枚举当前行的每一种情况
if (can[j]) {
for (int h = 0; h <= all; ++h) { // 枚举上一行的每一种情况
if (!can[h])
continue;
if (j & h) // 判断上下是否紧挨着
continue;
if ((j << 1) & h) // 判断当前国王的位置是否是上一行国王位置的右下角
continue;
if ((j >> 1) & h) // 判断当前国王的位置是否是上一行国王位置的左下角
continue;
for (int l = k; l >= need[j]; --l) { // 枚举国王个数
dp[i][j][l] += dp[i - 1][h][l - need[j]];
}
}
}
}
}
ll ans = 0;
for (int i = 0; i <= all; ++i) { // 统计答案
ans += dp[n][i][k];
}
printf("%lld\n", ans);
return 0;
}
作者:yifan0305
出处:https://www.cnblogs.com/yifan0305/p/16683526.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
转载时还请标明出处哟!
朝气蓬勃 后生可畏
分类:
刷题记录
Buy me a cup of coffee ☕.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通