高维前缀和

Hugoi / 2024-10-23 / 原文

原来我不会。

看不懂只能自己编了。

子集枚举

枚举一个集合的每个子集的所有子集。

for(int s=0;s<(1<<n);s++){
    for(int s0=s;;s0=s0&(s0-1)){
        cout<<s0<<' ';
    }
}

其中 \(s0\) 即为枚举的每个子集的所有子集。

这是为什么?

第一层循环中,我们枚举了一个子集。

那么第二层循环中,我们就要枚举这个子集的所有子集。

\(s\) 本身开始,从大往小降序枚举。

我们想快速从一个子集跳转到比它小的第一个子集。

分类讨论一下:

若当前的子集最低位为 \(1\),比它小的第一个子集显然是它 \(-1\)

否则,当前子集最低位为 \(0\),考虑 \(-1\) 以后会发生什么变化。

发现,会将第一段后缀 \(10000000……\) 变成 \(011111111……\),也就是让最低位的 \(1\) 变成了 \(0\),最低位之前的 \(0\) 都变成了 \(1\)

此时只需保留原集合中的所有位置即可,所以 \(\&s\) 就行了。

发现我们的枚举不重不漏,那么复杂度就是所有子集的子集个数的和。

\(\sum_{i=0}^{n}C_{n}^{i}2^i=3^n\)

高维前缀和

对于原集合的每一个子集,都有一个初始权值,现在求每一个子集的子集权值和。

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

这该如何理解?

考虑一位一位地加入贡献。

或者说,每次枚举时都将高位的位置钦定不变,某一个 \(1\) 位钦定为 \(0\),只考虑低位位置子集所带来的贡献。

这么说太抽象了,举个例子吧。

假设当前枚举到了第 \(2\) 位,要贡献到 \(11111\)

那么我们就让当前的 \(11101\) 对它贡献。

此时,高位的三位是不变的,而 \(11101\) 已经是一个前三位不变,后两位是子集和的东西了。

\(11101\) 此时已经是 \(11100\)\(11101\) 的和了。

那么就可以直接让它贡献到 \(11111\) 了。

当枚举到第三位时,我们钦定的就是高位的两位不变了。

对于 \(11111\) ,现在我们让 \(11011\) 贡献到它。

此时,\(11011\) 也是钦定了前两位,后两位的子集和了。

\(11011\) 此时是原来的 \(11000,11001,11010,11011\) 的和了。

那么也可以让它贡献到 \(11111\) 了。

类似的考虑问题即可。

由于每次钦定的 \(0\) 的位置不同,所以不重不漏。

超集是同理的,每次钦定高位不变,当前 \(0\) 位为 \(1\),考虑低位的超集带来的贡献即可。

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