高维前缀和(sos dp)

Posted Kidulthood

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了高维前缀和(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;
}

未完待续---

以上是关于高维前缀和(sos dp)的主要内容,如果未能解决你的问题,请参考以下文章

SPOJ Time Limit Exceeded(高维前缀和)

Codeforces 449D:Jzzhu and Numbers(高维前缀和)

高维前缀和

SP2829 TLE (FWT)

SOS dp

SOS问题