【动态规划】状态压缩动态规划

一、集合类状态压缩动态规划

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;
}

猜你喜欢

转载自blog.csdn.net/weixin_45697774/article/details/108293418