目录
一、集合类状态压缩动态规划
A、 AcWing 91. 最短Hamilton路径
状压DP基础应用
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int f[1 << 20][20];
int w[20][20];
int n, m;
int main(){
scanf("%d", &n);
for(int i = 0; i < n; ++ i)
for(int j = 0; j < n; ++ j)
scanf("%d", &w[i][j]);
memset(f, 0x3f, sizeof f);
f[1][0] = 0;
for(int i = 0; i <= (1 << n) - 1; ++ i)
for(int j = 0; j < n; ++ j){
if((i >> j) & 1){
for(int k = 0; k < n; ++ k){
if(i ^ (1 << j) >> k & 1){
f[i][j] = min(f[i][j], f[i ^ (1 << j)][k] + w[k][j]);
}
}
}
}
printf("%d\n", f[(1 << n) - 1][n - 1]);
return 0;
}
B、AcWing 524. 愤怒的小鸟
二、连通类(棋盘类)状态压缩动态规划
所有连通类(棋盘类)状态压缩动态规划 都分两步走:
- 1.预处理,保存所有合法转移方案
- 2.初始化,开始转移
A、AcWing 291. 蒙德里安的梦想
闫氏DP大法好
我们这里是一列一列地来,因为是一个棋盘性的状态压缩DP,从哪个方向都一样
摆放的小方格总方案数
等价于 横着摆放的小方格总方案数
遍历每一列,i列的方案数只和i-1
列有关系
第i - 1
行的状态k
能转移到第i
行的状态j
,当且仅当
j & k == 0
, 即第i
列和第i - 1
列的每一行都没有连续两个横向棋子重叠放置st[j | k] == true i - 1
即第i
列和第i - 1
列合并以后(要合并是因为第i
列和第i - 1
列都是两个空格子才行,一列有竖棋子的上半部分一列没有是不算的)的状态没有出现连续空格子为奇数个
状态表示 f[i][j]
: 前i - 1
列已经确定,且从第i - 1
列伸出的小方格在第i
列的状态为j
的方案数。属性:方案数取总和
。
状态计算:(限制条件:i - 1
列非空白位置可以不能放置小方格),在i
列不同的放置方法就是不同的集合划分。
#include<cstdio>
#include<algorithm>
#include<cstring>
typedef long long ll;
using namespace std;
ll f[12][1 << 11];
bool vis[1 << 11];
int n, m, cnt;
int main()
{
while(~scanf("%d%d", &n, &m) && n + m){
//我们一列一列地来
for(int i = 0; i < (1 << n); ++ i){//先预处理合法状态
vis[i] = true;//初始化
cnt = 0;//初始化
for(int j = 0; j < n; ++ j){//n行所以有n个状态
if(i >> j & 1){//遇见了横着放的棋子
if(cnt & 1){//此时空的格子(允许防止竖棋子的格子,必须是偶数因为竖棋子要连着占两格)如果为奇数个说明不合法
vis[i] = false;//标记此状态不合法
break;
}
}
else cnt ++ ;//记录连续的空格子数,因为只需要知道是奇数还是偶数所以可以一直加
}
if(cnt & 1)vis[i] = false;//同理,如果最后的连续的空格子是奇数个说明也不合法
}
memset(f, 0, sizeof f);//多组数据清零
f[0][0] = 1;
for(int i = 1 ;i <= m; ++ i){
for(int j = 0; j < (1 << n); ++ j){
for(int k = 0; k < (1 << n); ++ k){
if((j & k) == 0 && vis[j | k]){
f[i][j] += f[i - 1][k];
}
}
}
}
printf("%lld\n", f[m][0]);
}
return 0;
}
B、AcWing 1064. 小国王
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<vector>
typedef long long ll;
using namespace std;
const int N = 12 , K = 110;
int n, k;
vector<int>state;
vector<int>head[1 << N];
ll f[N][K][1 << N];
int cnt[1 << N];
bool check(int state)
{
for(int i = 0; i < n; ++ i)
if((state >> i) & 1 && state >> (i + 1) & 1)
return false;
return true;
}
int counts(int state)
{
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){
if(check(i)){
state.push_back(i);
cnt[i] = counts(i);
}
}
for(int i = 0; i < (int)state.size(); ++ i){
for(int j = 0; j < (int)state.size(); ++ j){
int a = state[i], b = state[j];
if((a & b) == 0 && check(a | b))
head[i].push_back(j);
}
}
/*2.初始化,开始转移*/
f[0][0][0] = 1;
for(int i = 1; i <= n + 1; ++ i){
for(int j = 0;j <= k; ++ j){
for(int a = 0; a < (int)state.size(); ++ a){
for(int b : head[a]){
int c = cnt[state[a]];
if(j >= c)
f[i][j][state[a]] += f[i - 1][j - c][state[b]];
}
}
}
}
cout << f[n + 1][k][0] << endl;
return 0;
}
C、AcWing 327. 玉米田
跟上一题类似。
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
typedef long long ll;
const int N = 13, mod = 1e8;
vector<int>state;
vector<int>head[1 << N];
int n, m;
int g[N];
ll f[N][1 << N];
bool check(int state){
for(int i = 0; i < m; ++ i)
if((state >> i) & 1 && (state >> (i + 1)) & 1)
return false;
return true;
}
int main(){
cin >> n >> m;
for(int i = 1; i <= n; ++ i)//1~n行,要空一行f[0][0] = 1作为起点这样输出为f[n+1][0];
for(int j = 0; j < m; ++ j){
int x;
scanf("%d", &x);
g[i] += !x << j;//把0转成1,并转换成二进制方便与待选状态匹配
}
for(int i = 0; i < (1 << m); ++ i)
if(check(i))
state.push_back(i);
for(int i = 0; i < (int)state.size(); ++ i){
for(int j = 0; j < (int)state.size(); ++ j){
int a = state[i], b = state[j];
if((a & b) == 0){
head[i].push_back(j);
}
}
}
f[0][0] = 1;
for(int i = 1; i <= n + 1; ++ i){
for(int a = 0; a < (int)state.size(); ++ a){
if((state[a] & g[i]) == 0)//无冲突
for(int b : head[a]){//最后的head里的状态不用再跟g数组筛选因为不合法的一直为0
f[i][a] = (f[i][a] + f[i - 1][b]) % mod;
//f[i][state[a]] += f[i - 1][state[b]];
}
}
}
cout << f[n + 1][0];
}
D、AcWing 292. 炮兵阵地
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<vector>
using namespace std;
const int N = 11, M = 110;
int n, m;
int g[1 << N];
int f[2][1 << N][1 << N];
vector<int>state;
int cnt[1 << N];
//横着判断
bool check(int state){
for(int i = 0; i < m; ++ i)
if((state >> i & 1) && ((state >> i + 1 & 1) || (state >> i + 2 & 1)))
return false;
return true;
}
int count(int state){
int res = 0;
for(int i = 0; i < m; ++ i)
if(state >> i & 1)
res ++ ;
return res;
}
int main(){
cin >> n >> m;
for(int i = 1; i <= n; ++ i){
for(int j = 0; j < m; ++ j){
char ch;
//ch = getchar();
cin >> ch;
g[i] += (ch == 'H') << j;
}
}
for(int i = 0; i < (1 << m); ++ i)
if(check(i)){
state.push_back(i);
cnt[i] = count(i);
}
//表示当前行状态为state[k],上一行状态为state[j],上上一行状态为state[l]
//由j转移到k,一行一行转移
for(int i = 1; i <= n; ++ i)
for(int j = 0; j < (int)state.size(); ++ j)
for(int k = 0; k < (int)state.size(); ++ k)
for(int l = 0; l < (int)state.size(); ++ l){
int a = state[j], b = state[k], c = state[l];
//竖着判断
if(a & b || a & c || b & c)continue;
if(g[i] & b || g[i - 1] & a)continue;
f[i & 1][j][k] = max(f[i & 1][j][k], f[i - 1 & 1][l][j] + cnt[b]);
}
int res = 0;
for(int i = 0; i < (int)state.size(); ++ i)
for(int j = 0; j < (int)state.size(); ++ j){
res = max(res, f[n & 1][i][j]);
}
cout << res << endl;
return 0;
}