高维前缀和(sos dp)
来看以下例题:令 \(a_i\) 为 \(\sum_{j \subsetneq i}a_j\)。你会说,这简单啊,枚举子集直接 \(\mathcal O(3^n)\)。
好的,有没有什么更优秀的算法呢?引入高维前缀和。就比如我们要求一个三维的前缀和,需要写一个比较长的容斥式,并且扩展到多维转移的时间复杂度为 \(2^k\)。其实,我们duck不必这样,直接一维一维转移就行了。
for(int i = 1; i <= n; i ++) {
for(int j = 1; j <= n; j ++) {
for(int k = 1; k <= n; k ++) pre[i][j][k] += pre[i - 1][j][k];
}
}
for(int i = 1; i <= n; i ++) {
for(int j = 1; j <= n; j ++) {
for(int k = 1; k <= n; k ++) pre[i][j][k] += pre[i][j - 1][k];
}
}
for(int i = 1; i <= n; i ++) {
for(int j = 1; j <= n; j ++) {
for(int k = 1; k <= n; k ++) pre[i][j][k] += pre[i][j][k - 1];
}
}
这个看一看应该都能懂吧。(
好的,回到这道题,直接给出递推式。
for(int i = 0; i < n; i ++) {
for(int j = 0; j < (1 << n); j ++) {
if((j >> i) & 1) dp[j] += dp[j ^ (1 << i)];
}
}
乍一看就是一个状压,但枚举 \(1\to n\) 被放在了外面。可以把这个状态理解为 \(n\) 维只有长度为 \(1\) 的高维前缀和,你会发现这和上面那段代码本质上是一样的。
并且,这里可以扩展,不仅仅是 \(2\) 进制,令其为 \(k\) 进制,时间复杂度为 \(\mathcal {n\times k^n}\)。
CF165E Compatible Numbers
啊,其实不难。令全集为 \(U\),目标数为 \(ans\),则一定有 \(ans\ \&\ (U \oplus a_i)=ans\)。用 sos dp 维护一下就可以了。
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <climits>
#include <cstring>
using namespace std;
const int MAXN = 1e6 + 5, MAXM = (1 << 22) + 5;
int n, a[MAXN], dp[MAXM], t, q;
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i ++) scanf("%d", &a[i]), dp[a[i]] = a[i], t = max(t, a[i]); t = log(t) / log(2) + 1;
for(int i = 0; i < t; i ++) for(int j = 0; j < (1 << t); j ++) if((j >> i) & 1) if(dp[j ^ (1 << i)]) dp[j] = dp[j ^ (1 << i)];
for(int i = 1; i <= n; i ++) q = ((1 << t) - 1) ^ a[i], printf("%d ", dp[q] ? dp[q] : -1);
return 0;
}
CF1208F
注意 sos dp 是不能动态做(即动态插入数)的。(废话)
发现后面那个 \(a_j \& a_k\) 是可以用 sos dp 维护的。然后再遍历 \(a_i\) 就行了。具体地,令 \(dp[S][2]\) 为 \(S\) 所有超集下标的最大值和次大值,遍历 \(a_i\) 时贪心地从高位到低位加,判断其次大值是否比 \(i\) 大即可。
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <climits>
#include <cstring>
using namespace std;
const int MAXN = 1e6 + 5, MAXM = (1 << 21) + 5;
int n, a[MAXN], t, dp[MAXM][2], ans;
int Max(int x, int y) { return x > y ? x : y; }
void Insert(int S, int val) {
if(val > dp[S][0]) dp[S][1] = dp[S][0], dp[S][0] = val;
else dp[S][1] = Max(dp[S][1], val);
}
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i ++) scanf("%d", &a[i]), Insert(a[i], i), t = max(t, a[i]);
t = log(t) / log(2) + 1;
for(int i = 0; i < t; i ++) {
for(int j = 0; j < (1 << t); j ++) {
if(!((j >> i) & 1)) Insert(j, dp[j ^ (1 << i)][0]), Insert(j, dp[j ^ (1 << i)][1]);
}
}
for(int i = 1; i <= n; i ++) {
int res = 0;
for(int j = t - 1; j >= 0; j --) {
if((a[i] >> j) & 1) continue;
if(dp[res | (1 << j)][1] > i) res |= (1 << j);
}
if(res || dp[res][1] > i) ans = Max(ans, res | a[i]);
}
printf("%d", ans);
return 0;
}
未完待续(咕咕咕~