reference: https://codeforces.com/blog/entry/45223
前置技能: 状态压缩 DP
SoS-DP 全称是 Sum over Subsets Dynamic Programming
,即子集和 DP,属于状态压缩 DP 中的一个经典问题的优化。
1. 问题引入
求 f [ s ] = ∑ i ∈ s a [ i ] f[s]=\sum\limits_{i\in s} a[i] f[s]=i∈s∑a[i] 或 f [ i ] = ∑ i ∈ s a [ s ] f[i]=\sum\limits_{i\in s} a[s] f[i]=i∈s∑a[s],我们拿出第一个问题来讨论。
2. 朴素写法
O ( 4 n ) \mathcal{O}(4^n) O(4n) 暴力枚举:
int up = 1 << n;
for (int s = 0; s < up; s++) {
for (int i = 0; i < up; i++) {
if (s & i == i) f[s] += a[i];
}
}
3. 稍微优化
O ( 3 n ) \mathcal{O}(3^n) O(3n) 枚举子集:
for (int s = 0; s < up; s++) {
f[s] = a[0];
for (int i = s; i > 0; i = (i-1)&s) f[s] += f[i];
}
4. 使劲优化
用 f [ s ] [ i ] f[s][i] f[s][i] 表示 x & s = x x\&s=x x&s=x 且 x ⊕ s < 2 i + 1 x\oplus s<2^{i+1} x⊕s<2i+1 的 a [ x ] a[x] a[x] 的和,有 f [ s ] [ i ] = f [ s ] [ i − 1 ] + f [ s ⊕ 2 i ] [ i − 1 ] f[s][i]=f[s][i-1]+f[s\oplus2^i][i-1] f[s][i]=f[s][i−1]+f[s⊕2i][i−1],转移图如下:
那么我们就有了下面的写法:
// iterative version
for (int s = 0; s < up; s++) {
dp[s][-1] = a[s];
for (int i = 0; i < n; i++) {
if (s & (1 << i)) dp[s][i] = dp[s][i-1] + dp[s^(1<<i)][i-1];
else dp[s][i] = dp[s][i-1];
}
f[s] = dp[s][n-1];
}
// memory optimized version
for (int s = 0; s < up; s++) f[s] = a[s];
for (int i = 0; i < n; i++) {
for (int s = 0; s < up; s++) {
if (s & (1 << i)) f[s] += f[s^(1<<i)]; // f[s^(1<<i)] += f[s]
}
}
可以证明这个时间复杂度是 O ( n ⋅ 2 n ) \mathcal{O}(n\cdot2^n) O(n⋅2n) 非常优秀。