高维前缀和
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\)” 的点。如果我们将整个序列取反(取补集),那么超集就会变成子集,子集就会变成超集,前后缀的计算也会颠倒。
子集与超集的关系
作子集前缀和时,每个子集都会为其超集贡献。这很像我们作一维前缀和时 \(a_i\) 会为 \(s_j(i\le j)\) 贡献一样。即超集是子集的高维前缀和。
所以我们如果将超集数据差分(即让每个集合吐出它子集给它的贡献),就得到了子集数据。
试一试:CF449D.

浙公网安备 33010602011771号