寒假集训·状压DP子集枚举2
P3052的加强版
与原版不同,这里的数据范围从最大为 181818 变为了 222222 ,这就意味着我们只能使用 2n∗n2^n*n2n∗n 的时间复杂度,那我们就必须把dp数组变成一维。
dp[s]dp[s]dp[s] 就表示在 sss 状态下,所需的最小电梯数;
因为我们还要看能不能再装一头牛,所以我们还需要存最后一个电梯的重量。
cnt[s]cnt[s]cnt[s] 就表示在 sss 状态下,最后一个电梯的已用最小空间。
还有一个需要注意的点
dp[0]=1;dp[0]=1;dp[0]=1;
因为这里 cnt[0]=0cnt[0]=0cnt[0]=0,意思就是最后一个(第一个)电梯装了 000 磅,那也应该写用了1个电梯。
代码:
cpp
#include<bits/stdc++.h>
using namespace std;
int n, m;
int c[25];
int dp[(1 << 22) + 5];
int cnt[(1 << 22) + 5];
int main() {
cin >> n >> m;
for (int i = 0; i < n; i++) {
cin >> c[i];
}
memset(dp, 0x3f, sizeof dp);
memset(cnt, 0x3f, sizeof cnt);
dp[0] = 1;
cnt[0] = 0;
for (int s = 0; s < (1 << n); s++) {
for (int i = 0; i < n; i++) {
if (!(s & (1 << i))) {
if (cnt[s] + c[i] <= m) {
dp[s + (1 << i)] = min(dp[s + (1 << i)], dp[s]);
cnt[s + (1 << i)] = min(cnt[s + (1 << i)], cnt[s] + c[i]);
} else {
dp[s + (1 << i)] = min(dp[s + (1 << i)], dp[s] + 1);
cnt[s + (1 << i)] = min(cnt[s + (1 << i)], c[i]);
}
}
}
}
for (int s = 0; s < (1 << n); s++) {
// cout << "dp[" << s << "]: " << dp[s] << " cnt[" << s << "]: " << cnt[s] << '\n';
}
// cout << '\n';
cout << dp[(1 << n) - 1];
return 0;
}
P5911 [POI 2004] PRZ
变式题,在上一道题的基础上计算答案的方法变成了取最慢的人,其他大概逻辑没有很大变化。
AT_dp_u Grouping
可以经过计算得出:此时时间复杂度为 3n3^n3n ,卡着过,所以枚举子集里不能有其他东西,但是我们又要获取状态 sss 的相性和,怎么办?预处理。
cpp
for (int s = 0; s < (1 << n); s++) { //状态
for (int i = 0; i < n; i++) { //枚举兔子i
for (int j = i + 1; j < n; j++) { //枚举兔子j
if ((s & (1 << i)) && (s & (1 << j))) { //都选了
pre[s] += dis[i][j];
}
}
}
}
转移方程:
dp[s] = max(dp[s], dp[s ^ t] + pre[t]);
代码:
cpp
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
int dis[25][25];
ll pre[(1 << 16) + 5];
ll dp[(1 << 16) + 5];
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
cin >> dis[i][j];
}
}
for (int s = 0; s < (1 << n); s++) {
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if ((s & (1 << i)) && (s & (1 << j))) {
pre[s] += dis[i][j];
}
}
}
}
// for (int s = 0; s < (1 << n); s++) {
// cout << pre[s] << ' ';
// }
// cout << '\n';
dp[0] = 0;
for (int s = 0; s < (1 << n); s++) {
for (int t = s; t; t = (t - 1)&s) {
dp[s] = max(dp[s], dp[s ^ t] + pre[t]);
}
}
// for (int s = 0; s < (1 << n); s++) {
// cout << dp[s] << ' ';
// }
// cout << '\n';
cout << dp[(1 << n) - 1];
return 0;
}
P2167 [SDOI2009] Bill的挑战
定义 dp[i][mask]dp[i][mask]dp[i][mask] 表示构造到第 iii 位时,匹配的字符串集合为 maskmaskmask 的方案数;
预处理数组:f[i][ch]f[i][ch]f[i][ch] 表示第 iii 位填入字符 chchch 时,能匹配的字符串集合(二进制 maskmaskmask ),计算规则为:遍历所有字符串,若该字符串第 iii 位是 ??? 或等于 chchch,则对应位为 111;
状态转移:
dp[i+1][mask & f[i][ch]] += dp[i][mask]
当前位选字符 chchch 时,新的匹配状态为原状态与 f[i][ch]f[i][ch]f[i][ch] 的交集,方案数累加
初始化:dp[0][(1<<n)-1] = 1(第 000位未构造时,所有字符串都处于"匹配"状态,方案数为 111)。
P3694 邦邦的大合唱站队
前缀和数组 pre[j][i]pre[j][i]pre[j][i](0-based):表示原队列前 i+1i+1i+1 个位置(0~i)中,第 jjj 支乐队(0-based)的偶像数量,用于 O(1)O(1)O(1) 查询任意区间内某乐队的人数;
计数数组 f[j]f[j]f[j]:统计第 jjj 支乐队的总人数;
状态和数组 sum[mask]sum[mask]sum[mask]:表示 maskmaskmask 对应的乐队集合的总人数( maskmaskmask 为二进制状态,第 jjj 位为 111 表示选了第j支乐队)。
状态定义:dp[mask]dp[mask]dp[mask] 表示选 maskmaskmask 对应的乐队集合时,使这些乐队连续排列的最少出列人数;
初始化:dp[0] = 0(选 000 支乐队时出列人数为 000),其余初始化为极大值;
状态转移:遍历每个状态 maskmaskmask,尝试加入未选的乐队j,计算该乐队占据区间 [sum[mask][sum[mask][sum[mask], sum[mask]+f[j]-1]的出列成本(成本=区间长度−区间内原有的j乐队人数成本=区间长度-区间内原有的j乐队人数成本=区间长度−区间内原有的j乐队人数),更新dp[mask | (1<<j)] = min(原值, dp[mask]+成本);