高维前缀和

25.06.26

其他进制下的高维前缀和

高维前缀和理论上可以手动容斥,但维数高时不仅不方便还要命。

这时我们就可以采用另一种写法,对每一维依次作前缀和。

for(int i=1;i<=n;i++){
  for(int j=1;j<=n;j++) ...
    f[i][j]...[]+=f[i-1][j]...[];
}
for(int i=1;i<=n;i++){
  for(int j=1;j<=n;j++) ...
    f[i][j]...[]+=f[i][j-1]...[];
}
...

如果将下标改为从 \(0\) 开始,\(n\) 就可以看作进制。

我们将 \(t\) 维的长度为 \(n\) 的信息压缩为一个 \(n\) 进制数,就可以将原来的代码简化如下:

for(int i=0;i<t;i++){
	for(int j=0;j<pw[t];j++){
		if(j/pw[i]%n) f[j]+=f[j-pw[i]];
	}
}

试一试:ARC136D.

二进制下的高维前缀和

把每个在全集中的元素的存在与否看作一“维”,并压缩为一个二进制数,就可以进行子集、超集相关的计算。

子集和超集(母集)

子集:若 \(A\)\(B\) 的子集,则 \(A|B=B\) (这里 \(|\) 是按位或运算),即 \(\forall x\notin B, x\notin A\).
超集(母集):若 \(A\)\(B\) 的超集,则 \(A\&B=B\) (这里 \(\&\) 是按位与运算), 即 \(\forall x\in B, x\in A\). (等价于 \(B\)\(A\) 的子集)。

子集/母集的前缀和(SOSdp, Sum Over Subsets DP)

子集前缀和,即 \(f_j=\sum_{i\subset j}f_i\).

for(int j=0;j<LG;j++){
	for(int i=0;i<(1<<LG);i++){
		if(i>>j&1) f[i]+=f[i^(1<<j)];
	}
}

母集前缀和(或高维后缀和),即 \(f_j=\sum_{j\subset i}f_i\).

for(int j=0;j<LG;j++){
	for(int i=(1<<LG)-1;i>=0;i--){
		if((i>>j&1)==0) f[i]+=f[i^(1<<j)];
	}
}

或写法 2:

for(int i=0;i<(1<<LG);i++){
	for(int j=i;j<(1<<LG);j=(j+1)|i) f[i]+=f[j];
}

或许你会疑惑写法 2 的复杂度为什么是 \(O(n\log n)\),这时因为第二层循环中 \(j\) 在二进制表示下 \(1\) 的数量(即popcount(j)) 每次都 \(+1\).

前缀和与后缀和的区别在于,我们想要将贡献加给“第 \(k\) 位是 \(1\)”的点还是“第 \(k\) 位是 \(0\)” 的点。如果我们将整个序列取反(取补集),那么超集就会变成子集,子集就会变成超集,前后缀的计算也会颠倒。

试一试:ARC100C, CF165E, CF383E.

子集与超集的关系

作子集前缀和时,每个子集都会为其超集贡献。这很像我们作一维前缀和时 \(a_i\) 会为 \(s_j(i\le j)\) 贡献一样。即超集是子集的高维前缀和。

所以我们如果将超集数据差分(即让每个集合吐出它子集给它的贡献),就得到了子集数据。

试一试:CF449D.

参考:这篇这篇

posted @ 2025-11-26 19:35  xwxabc  阅读(1)  评论(0)    收藏  举报