状压 dp
引入
先看一道例题:(可能 r18)
有
我们显然可以暴力枚举每一个男生跟谁组 CP 然后判断是否合法。但是这方法
我们可以设计出一个 pure and simple 的状态:
转移不难想:只要第
但是,你还要写 if
用于输出答案(或许不用?),谁想啊......
考虑到 int
存的下,所以尝试将之前二十个维度压成一个维度。
此即“状态压缩”dp,简称 状压 dp。
状压 dp 常见套路
可行性问题转换最优化问题
Moovie Mooving G
奶牛 Bessie 想连续看
Bessie 可以在一部电影播放过程中的任何时间进入或退出放映厅。但她不愿意重复看到一部电影,所以每部电影她最多看到一次。她也不能在看一部电影的过程中,换到另一个正在播放相同电影的放映厅。
请帮 Bessie 计算她能够做到从
很显然,直接做似乎不行了......
我们发现,只要某个方案能够耗掉
最小化背包数量
有
现在我们要将这些物品放进某些背包满足没有背包超载。求最少有多少个背包里面有物品。
Naive
定义
枚举开一个新背包里面装某些东西。
枚举原先集合
Improvment 1
考虑到我们只需要对没放的东西新开一个背包。
所以仅需枚举剩下东西的子集。
时间复杂度
考虑选了正好
Improvment 2
其实我们只需要枚举下一个要放什么元素,然后转移即可。
时间复杂度纯粹的
按行 dp
有 0
", "1
", 或 "?
" 。每个 "?
" 会被等概率替换为 "0
" 或 "1
" 。请计算网格图中不存在两个相邻网格均为 "1
" 的 概率。 本题中我们认为网格
我们定义
Naive
直接枚举当前行和之前行,校验。
Inprovment 1
这一行的 1
上一行不会有,枚举子集。
Improvment 2
校验的环节可以
总时间复杂度
Code:
#include <bits/stdc++.h> using namespace std; const int kMod = 998244353; int n, m, dp[16][1 << 16], cnt, sm, cz[16][1 << 16]; char c[22][22]; bool check(int x, int y) { for(int j = 0; j < m; j++) { //cout << c[x][j] if((((y >> j) & 1) && c[x][j] == '0') || (!((y >> j) & 1) && c[x][j] == '1')) { return 0; } } return !(y & (y >> 1)); } bool check2(int x, int y) { for(int j = 0; j < m; j++) { if(((x >> j) & 1) && ((y >> j) & 1)) { return 0; } } return 1; } int qpow(int x, int y) { int ans = 1; for(; y; y >>= 1) { if(y & 1) { ans = 1ll * ans * x % kMod; } x = 1ll * x * x % kMod; } return ans; } int qinv(int x) { return qpow(x, kMod - 2); } int main() { cin >> n >> m; for(int i = 1; i <= n; i++) { for(int j = 0; j < m; j++) { cin >> c[i][j]; cnt += c[i][j] == '?'; } } dp[0][0] = 1; int tmp = 0; //cout << check(1, 0) << '\n'; for(int i = 1; i <= n; i++) { for(int j = 0; j < (1 << m); j++) { cz[i][j] = check(i, j); } } cz[0][0] = 1; for(int i = 1; i <= n; i++) { for(int j = 0; j < (1 << m); j++) { if(cz[i][j]) { for(int k = (1 << m) - 1 - j; ; k = (k - 1) & ((1 << m) - 1 - j)) { //cout << i << ' ' << j << ' ' << k << '\n'; if(cz[i - 1][k]) { //cout << i << ' ' << k << '\n'; dp[i][j] = (dp[i][j] + dp[i - 1][k]) % kMod; } if(!k) { break; } } } if(i == n) { tmp = (tmp + dp[i][j]) % kMod; } //cout << dp[i][j] << ' '; } //cout << '\n'; } cout << 1ll * tmp * qinv(qpow(2, cnt)) % kMod << '\n'; return 0; }
(CodeBlocks 炸了,所以没有换行)
利用转移的特殊性状压转移
一排
-
与 互质; -
。
请问你至多可以组出几只二人队伍。每个玩家不能加入超过一个队伍。
注意到
直接状压
不过这题实现起来超级恶心,所以我放一个代码用来查错:
//cout << dp[n][j] << ' '; #include <bits/stdc++.h> using namespace std; int n, k, arr[100010], dp[100010][1 << 8], xg[100010][10]; int gcd(int x, int y) { return !y? x : gcd(y, x % y); } int main() { cin >> n >> k; for(int i = 1; i <= n; i++) { cin >> arr[i]; for(int j = 1; j <= min(i - 1, k); j++) { xg[i][j] = gcd(arr[i], arr[i - j]) == 1; } } // 这里写了扩散 for(int i = 1; i <= n; i++) { for(int j = 0; j < (1 << min(k, i)); j++) { // i 可以小于 K int tmp = (j << 1) & ((1 << k) - 1); dp[i + 1][tmp] = max(dp[i + 1][tmp], dp[i][j]); for(int x = 1; x <= min(i, k); x++) { if(!((j >> (x - 1)) & 1) && xg[i + 1][x]) { // 还可以配对 dp[i + 1][(tmp | (1 << x) | 1) & ((1 << k) - 1)] = max(dp[i + 1][(tmp | (1 << x) | 1) & ((1 << k) - 1)], dp[i][j] + 1); // 注意这里 & ((1 << k) - 1),否则 WA } } } } int ans = 0; for(int j = 0; j < (1 << min(k, n)); j++) { // N 可以小于 K ans = max(ans, dp[n][j]); //cout << dp[n][j] << ' '; } cout << ans << '\n'; return 0; }
祝大家 for(;;) rp += INT_MAX;
!
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析