高维前缀和详解
高维前缀和详解
背景:
sensei:我们随便上点技巧类型的东西吧,就这个高位前缀和......(讲了一堆k维前缀和复杂度证明后)......好我们看看版题。
版题:
现在有n(n≤20)个物品,确定每个物品的选取与否可以表示一个集合,那么这n个物品最多可以表示个2的n次方个集合。
现在对每个集合都给定个权值,然后查询某个集合以及其包含的所有子集的权值和。
sensei:很简单吧,直接用高维就好了。
我:???高维在哪里???
现实版的高维前缀和(真就k维这样)
引入:
我们拿k=2来进行引入:
一般的二维前缀和是利用的容斥原理,加加减减四项好好写哦。
k=3,没关系,稍微复杂点而已。
k=20,没关系,自毙了而已。
OI大神:无所谓我可以慢慢熬。
sosdp:您加油,我先A了。
写法:
那么高级的前缀和怎么写:对于每一维上轮流求前缀和。
k=2:
for(int i=1;i<=n;++i)//先求数组a关于第一个维度的前缀和
{
for(int j=1;j<=m;++j)
{
a[i][j]=a[i][j]+a[i][j-1];
}
}
for(int i=1;i<=n;++i)//在已经求完一个维度前缀和的基础上求数组a关于第二个维度的前缀和
{
for(int j=1;j<=m;++j)
{
a[i][j]=a[i][j]+a[i-1][j];
}
}
k=3:
for(int i=1;i<=n;++i)
{
for(int j=1;j<=m;++j)
{
for(int k=1;k<=p;++k)
{
a[i][j][k]+=a[i-1][j][k];
}
}
}
for(int i=1;i<=n;++i)
{
for(int j=1;j<=m;++j)
{
for(int k=1;k<=p;++k)
{
a[i][j][k]+=a[i][j-1][k];
}
}
}
for(int i=1;i<=n;++i)
{
for(int j=1;j<=m;++j)
{
for(int k=1;k<=p;++k)
{
a[i][j][k]+=a[i][j][k-1];
}
}
}
这样写的意义:
假设我们求的是k维前缀和,对于原来的算法,我容斥原理要计算2的n次方次,已经不是常数了......
现在这个新的算法的时间就是k*容量了,大大减少时间复杂度和思考程度
抽象的高维前缀和
引入:
现实版高维前缀和倒下了。
某OIer:就这就这就这???
抽象版高维前缀和上场了。
OIer倒下了。
求解子集/超集:就这就这就这???
正文:
现在我们考虑抽象的高位前缀和:子集和超级。
我们先拿子集来说。
对于长度为2的情况,有四种情况:01/00/11/10。
放到二维平面上考虑
我们想求在这四种情况下的权值和,a00+a01+a10+a11,发现正是二维前缀和!
很显然这种情况可以扩展到k维,取到的前缀和就表示长度为k的子集的权值和。
然后我们会发现:a数组的k个维度都是0或者1,那我们可以用bitset或者一个二进制数直接表示整体(这样就不用开k维数组了)
代码如下:
for(int i=0;i<w;++i)//依次枚举每个维度
{
for(int j=0;j<(1<<w);++j)//求每个维度的前缀和
{
if(j&(1<<i))s[j]+=s[j^(1<<i)];
}
}
例题上理解
还是想要给一个例题,因为如果你被抽象的高维前缀和弄混淆意义的话,可以看后面的解释来完善理解:
https://www.luogu.com.cn/problem/CF165E
竟然有提示是高位前缀和,我们先往子集方向想
对于i这个数,什么时候会有答案?
我们将i取反,他的任意一个“子集”都可以作为答案。
预处理出数组 a 的高维前缀和,每一次询问时先取反,再看取反后子集是否为空。高维前缀和只需存储子集中的任意一个数即可。
看好代码注释,仔细想想这几个的问题:
权值是什么?初始化是什么?
如何在多个数中正好取到一个数(也和权值有关)。
为什么转移时,是等于而不是+=?
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int a[1000006];
int f[1<<22];
/*
将i这个数字取反,那么我现在要找他的子集
预处理出数组 a 的高维前缀和,每一次询问时先取反,再看取反后子集是否为空。高维前缀和只需存储子集中的任意一个数即可。
*/
signed main(){
ios::sync_with_stdio(false);
int n;
cin>>n;
for(int i=1;i<=n;i++) {
cin >> a[i];
f[a[i]]=a[i];//其实最开始就相当于初始化权值,这里让自己的子集就是自己完成初始化,没有的自然就是0
}
for(int i=0;i<=21;i++) {
for(int j=0;j<(1<<22);j++) {
if((j&(1<<i))&&f[j^(1<<i)]){
f[j]=f[j^(1<<i)];//这里为什么是等号呢,因为我的数字i的权值是i,这样就保证了有一个子集是数字i。
}
}
}//高维前缀和
for(int i=1;i<=n;i++) {
int b=((1<<22)-1)^a[i];//取反
if(f[b]!=0){
cout<<f[b]<<" ";
}
else cout<<-1<<" ";
}
}
本文作者:linghusama
本文链接:https://www.cnblogs.com/linghusama/p/17670590.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步