第一道状态压缩dp题
个人理解
关于状态压缩dp,也就是说把一个集合
状态用一个整型的二进制数来表示
试想一下如若表示一个集合内的所有状态情况那么就会开很多维度的数组,在空间和逻辑上都很难进行状态的转移;
题目大意:
在 n×n 的棋盘上放 k 个国王,国王可攻击相邻的 8 个格子,求使它们无法互相攻击的方案总数。
国王攻击形式如下:
问有多少摆放的方案;
思路:
这里考虑状态转移方程 f [ i ] [ k ] [ j ] f[i][k][j] f[i][k][j] 表示 已经放好了前 i 行和k个国王,第i行的状态为 j的 最大方案数;在考虑到第i行时我们只需考虑 i - 1 行的状态;
那么根据题意可以发现:
这里规定:第i行的状态 为j , 第 i- 1行的状态为k
- 一个状态不能有相邻的1(会互相攻击到
- j & k = 0 j\&k = 0 j&k=0
- j ∣ k j|k j∣k 不能有两个相邻的1;
棋盘类流程:
1. 处理出合法状态
2. 处理出合法状态之间的转移情况
3. dp
Code
/*
author:@bzdhxs
date:2021/11/12
URL:http://ybt.ssoier.cn:8088/problem_show.php?pid=1592
状态压缩DP模板;
*/
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<string>
#include<vector>
#include<map>
#include<queue>
using namespace std;
#define _orz ios::sync_with_stdio(false),cin.tie(0)
#define mem(str,num) memset(str,num,sizeof(str))
#define forr(i,a,b) for(int i = a, i <= b;i++)
#define forn(i,n) for(int i = 0; i < n; i++)
#define dbg() cout <<"0k!"<< endl;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 12, M = 1 << 10;
int n,k;
bool check(int i){
for(int j = 0; j < n; j++){
if((i>>j & 1) && (i >> j+1 & 1)) return false;
}
return true;
}
int count(int i){
int cnt = 0;
for(int j = 0 ; j < n;j++) if(i >> j & 1) cnt++;
return cnt;
}
ll f[N][N*N][M];
int main()
{
cin >> n >> k;
vector<int> q,cnt(M),get[M]; // 可行的状态
for(int i = 0; i < 1 << n;i++) if(check(i)) q.push_back(i);
for(int i = 0; i < q.size(); i++) cnt[i] = count(q[i]);
forn(i,q.size())forn(j,q.size()){
int a = q[i],b = q[j];
if((a&b) == 0 && check(a|b)) get[i].push_back(j); // 存储下标可以节省空间;
}
f[0][0][0] = 1;// 处理到第0行,一个国王不放的状态数为1
for(int i = 1; i <= n+1; i++)
for(int j = 0; j <= k; j++) // 枚举国王数量
for(int a = 0 ; a < q.size();a++) // 合法状态
for(auto b : get[a]){
// 每个合法状态可以转移的状态
int c = count(q[a]);// 当前状态已经放了几个国王
if(j >= c) f[i][j][a] += f[i-1][j-c][b];
}
cout << f[n+1][k][0] << endl; //
return 0;
}
引用彩笔dalao的题解!
// 滚动数组优化 因状态转移只用到了上一层的状态
for(int i = 1; i <= n+1; i++)
for(int j = 0; j <= k; j++)
for(int a = 0 ; a < q.size();a++){
f[i&1][j][a] = 0; // 清空 a 状态的数组!
for(auto b : get[a]){
int c = count(q[a]);
if(j >= c) f[i & 1][j][a] += f[i-1 & 1][j-c][b];
}
}
如有错误,还望指出!
ORZ!!