张嘉锡V

导航

 

状态机DP

  • 与背包dp不同,处在每个位置或时刻,可以有多种状态

1049. 大盗阿福

  • 对于每个状态,有选择和不选两种状态
  • 对于当前位置,如果选择,根据题目要求,前一个位置必须不能选
  • 如果不选当前位置,可以从前一个状态中转移过来,也可以从前两个状态转移过来

1057. 股票买卖 IV

初始化:

  • \(dp[i][j][1]\)初始时均为不合法,设置为负无穷
  • \(dp[i][j][0]\)设置为零

状态表示:

  • \(dp[i][j][0]\): 在前\(i\)天内,最多执行\(k\)此交易,当前手中无存货时,所得的最大利益
  • \(dp[i][j][1]\): 在前\(i\)天内,最多执行\(k\)此交易,当前手中有存货时,所得的最大利益

image

状态计算:

  • \(dp[i][j][0]\)
  1. 前一天手中无货:\(dp[i - 1][j][0]\)
  2. 前一天买入:\(dp[i - 1][j][1] - w[i]\)
  • \(dp[i][j][1]\)
  1. 前一天手中有货:\(dp[i - 1][j][1]\)
  2. 前一天买入:\(dp[i - 1][j - 1][0] - w[i]\)

结果:求最多交易\(k\)次,所获得的最大利益

1058. 股票买卖 V

状态表示:

  • \(dp[i][0]\): 在前\(i\)天内, 手中有存货
  • \(dp[i][1]\):在前\(i\)天内,手中无存货的第一天
  • \(dp[i][2]\):在前\(i\)天内,手中无存货了好几天(多于一天)

状态计算:

  • \(dp[i][0]\)
  1. \(dp[i - 1][2] - w[i]\):手中无存货了好几天,买入
  2. \(dp[i][0]\): 手中无存货
  • \(dp[i][1]\)
    \(dp[i - 1][0] + w[i]\):前一天手中有存货,卖掉了

  • \(dp[i][2]\)

  1. \(dp[i - 1][1]\):前一天是没有存货的第一天
  2. \(dp[i - 1][2]\):前一天也是没存货好几天了

状态压缩DP

  1. 基于连通性的DP(棋盘类)

291. 蒙德里安的梦想

  • 如果横放的长方形摆放好了,那么竖放的长方形的摆放的方案数就确定了,因此只需考虑横放的数量即可

  • \(dp[i][j]\)表示第\(i\)列,上一列中那些行伸出来的小方格状态数,伸出来记为1,否则值为零,由此产生的二进制数的十进制表示

  • 两个转移条件:

  1. \(i\) 列和 \(i - 1\)列同一行不同时捅出来:\((j & k) == 0\)
  2. 本列伸出来的状态\(j\)和上列捅出来的状态\(k\)求或,得到上列是否为偶数空行状态,如果是奇数空行不转移:
#include <iostream>
#include <cstring>
#include <vector>

using namespace std;
const int N = 12, M = 1 << N;
int n, m;
long long dp[N][M];
bool st[M];
vector<int> states[M];

int main() {
    while(cin >> n >> m , n || m) {
        for(int i = 0; i < 1 << n; i++) {
            st[i] = true;
            int cnt = 0;
            for(int j = 0; j < n; j++) {
                if(i >> j & 1) {
                    if(cnt & 1) {
                        st[i] = false;
                        break;
                    }
                }
                else cnt++;
            }
            if(cnt & 1) st[i] = false;
        }
        // cout << endl;
        for(int i = 0; i < 1 << n; i++) {
            states[i].clear();
            for(int j = 0; j < 1 << n; j++) {
                if((i & j) == 0 && st[i | j]) 
                    states[i].push_back(j);
            }
        }
        memset(dp, 0, sizeof dp);
        dp[0][0] = 1;
        for(int i = 1; i <= m; i++) {
            for(int j = 0; j < 1 << n; j++) {
                for(auto k : states[j]) {
                    dp[i][j] += dp[i - 1][k];
                }
            }
        }
        cout << dp[m][0] << endl;
    }
    return 0;
}

1064. 小国王

  • 状态表示:\(dp[i][j][s]\) 表示当前枚举到第\(i\)行,已经用了\(k\)个棋子,前一行棋子摆放的状态是s, 如果摆放记为1否则记为0,所形成的的十进制数
  • 合法方案:
    1. \(i - 1\)行内部不能有两个1相邻
    2. \(i - 1\)行和第\(i\)行之间不能互相攻击到
  • 状态计算:
    已经摆完前\(i\)排,第\(i\)排状态为\(a\),第\(i - 1\)排状态为\(b\),已经摆了\(j\)个国王的所有方案。
    已经摆完前\(i - 1\)排,第\(i - 1\)排状态为\(b\),已经摆了\(j - count(a)\)个国王的所有方案, \(dp[i - 1][j - count(a)][b]\)
#include <iostream>
#include <cstring>
#include <vector>

using namespace std;

typedef long long LL;
const int N = 12, M = 1 << 10, K = 110;
LL dp[N][K][M];
int cnt[M], n, m;
vector<int> h[M];
vector<int> states;

// 判断每行中是否中所有合法的状态:不含有相邻的不为1的数
bool check(int state) {
    for(int i = 0; i < n; i++) 
        if((state >> i & 1) && (state >> i + 1 & 1)) 
            return false;
    return true;
}

// 计算该状态在一行中放置的棋子数量
int count(int state) {
    int res = 0;
    for(int i = 0; i < n; i++) res += (state >> i & 1);
    return res;
}


int main() {
    cin >> n >> m;
    
    // 预处理各种可行的状态,存入states数组
    for(int i = 0; i < 1 << n; i++) {
        if(check(i)) {
            states.push_back(i);
            cnt[i] = count(i);
        }
    }
    
    // 枚举可行的状态,将两者可以作为上下行的存入h数组
    for(int i = 0; i < states.size(); i++) {
        for(int j = 0; j < states.size(); j++) {
            int a = states[i], b = states[j];
            if((a & b) == 0 && check(a | b)) 
                h[i].push_back(j);
        }
    }
    // 什么都没放的方案数为1
    dp[0][0][0] = 1;
    // 枚举每行
    for(int i = 1; i <= n + 1; i ++) {
        // 枚举所有的棋子
        for(int j = 0; j <= m; j++) {
            // 遍历所有的可行的状态
            for(int a = 0; a < states.size(); a++) {
                for(auto b : h[a]) {
                    int c = cnt[states[a]];
                    if(j >= c) 
                        dp[i][j][a] += dp[i - 1][j - c][b];
                }
            }
        }
    }
    cout << dp[n + 1][m][0];
    return 0;
}

292. 炮兵阵地

  • 状态表示:\(dp[i][j][k]\) 表示枚举到第\(i\)行,其上一行摆放的状态是\(j\), 上两行的状态为\(k\)
  • 状态计算:如果条件合法,\(dp[i][j][k] = max(dp[i - 1][j][l] + cnt[states[l]], dp[i][j][k])\)
  • 判断条件:
    1. 当前为平地
    2. 当前行、当前行的上一层、当前行的上两层,之间不能有交集
#include <iostream>
#include <vector>

using namespace std;
const int N = 110, M = 12;
int g[N], cnt[1 << M];
int n, m;
int dp[N][1 << M][1 << M];

vector<int> states;
vector<int> h[1 << M];
// 行与行之间不能有交集
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++) res += (state >> i & 1);
    return res;
}

int main() {
    cin >> n >> m;
    for(int i = 1; i <= n; i++) {
        for(int j = 0; j < m; j++) {
            char c; cin >> c;
            // 此处为山地,不能摆放炮团,标记为1
            g[i] += (c == 'H') << j;
        }
    }
    for(int i = 0; i < 1 << m; i++)
        if(check(i)) {
            states.push_back(i);
            cnt[i] = count(i);
        }
        

    for(int i = 1; i <= n + 2; i++)
        // 枚举i行状态
        for(int j = 0; j < states.size(); j++)
            // i - 1状态
            for(int k = 0; k < states.size(); k++)
                // i - 2状态
                for(int l = 0; l < states.size(); l++) {
                    int curr = states[j], r1 = states[k], r2 = states[l];
                    if((r1 & r2) | (r1 & curr) | (curr & r2)) continue;
                    if((g[i] & curr) | (g[i - 1] & r1))  continue;
                    dp[i & 1][j][k] = max(dp[i & 1][j][k], dp[i - 1 & 1][k][l] + cnt[curr]);
                }
    cout << dp[n + 2 & 1][0][0];
    return 0;
}
  1. 集合类状态压缩DP

91. 最短Hamilton路径

  • 将途径的点的状态压缩,途径的点记为1,未途径为0
  • \(dp[i][j]\) 表示到达\(j\)这个点时,最短的路径长度
  • 状态计算:\(dp[i][j] = min(dp[i][j], dp[i - (1 << j)][k] + a[k][j])\):
    找到更短的路径,从\(k\)转移过来,应该不途径第\(j\)个点,把j从经过的点集中去掉再加上从\(k\)\(j\)的距离
// 初始化
memset(dp, 0x3f, sizeof dp);
// 开始,途径第零个点
dp[1][0] = 0;
for(int i = 0; i < 1 << n; 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) 
                    dp[i][j] = min(dp[i][j], dp[i - (1 << j)][k] + a[k][j]);
            }
        }
cout << dp[(1 << n) - 1][n - 1];

524. 愤怒的小鸟

posted on 2020-07-18 14:06  张嘉锡V  阅读(389)  评论(0编辑  收藏  举报