最后冲刺!!!|

铃狐sama

园龄:1年11个月粉丝:5关注:5

高维前缀和详解

高维前缀和详解

背景:

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 中国大陆许可协议进行许可。

posted @   铃狐sama  阅读(163)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起