sosdp(高维前缀和)学习笔记

高维前缀和#

我们先看一维前缀和

for(int i=1;i<=n;i++)
s[i]+=s[i-1];

那么二维前缀和

for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];

这个是根据容斥计算的,维度很高的时候就不行了
我们换一种方法

for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
s[i][j]+=s[i-1][j];
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
s[i][j]+=s[i][j-1];

也就是对每一维分别做前缀和
那么高维前缀和呢?
它是解决以下问题的

例题#

已知一个序列a02n1,对于每一个i0,n1

j|i==iaj

也就是求它的子集的权值和
直接子集枚举做是3n的,我们需要更快的算法
考虑刚才前缀和的过程,发现对于二进制而言,它的子集一定是它的前缀和中的
因为一定会有某一位比这个数小,所以是前缀和
但是我们不可能按照容斥做法做前缀和,因此就对每一维做前缀和

for(int i=0;i<n;i++)
for(int j=0;j<(1<<n);j++)
if((j>>i)&1) f[j]+=f[j-(1<<i)];

复杂度O(n2n)
类似的,有下面的问题
已知一个序列a02n1,对于每一个i0,n1

j&i==iaj

也就是询问超集的权值和,对应到上面就是高维后缀和,和前缀和是类似的

for(int i=0;i<n;i++)
for(int j=0;j<(1<<n);j++)
if(!((j>>i)&1)) f[j]+=f[j+(1<<i)];

高维差分#

因为差分和前缀和是逆运算,所以就有了下面的代码

for(int i=0;i<n;i++)
for(int j=0;j<(1<<n);j++)
if((j>>i)&1) f[j]-=f[j-(1<<i)];

这个东西又被称作sosdp

[ARC100C] Or Plus Max#

题目传送门
在这里插入图片描述
注意到我们只需要求出

iorj==k

的最大值,取个前缀max就行了
但是这样也不好求,我们考虑继续转化为

iorjk

也就是k的子集,这个东西显然小于等于k,也是合法的
那么i,j显然也是k的子集,也就是说我么需要从k的子集中挑出最大值和次大值就行啦
那么我们知道前缀和不仅可以知道“和”,也可以知道前缀max之类的
那么我们只需要对于每一个k,求出他子集的最大和次大就可以了
可以用高维前缀和

#include<bits/stdc++.h>
using namespace std;
const int N = 19;
const int M = (1<<N);
typedef long long LL;
#define PII pair<LL,LL>
#define X(x) x.first
#define Y(x) x.second
PII Merge(PII A,PII B)
{
	PII res=A;
	if(X(B)>X(A))
	{
		X(res)=X(B);
		Y(res)=max(X(A),Y(B));
	} 
	else Y(res)=max(Y(A),X(B));
	return res;
}
PII f[M];
int main()
{
	int n;
	cin>>n;
	int m=(1<<n);
	for(int i=0;i<m;i++)
	{
		LL a;
		scanf("%lld",&a);
		f[i]=make_pair(a,(LL)-1e9+7);
	}
	for(int i=0;i<n;i++)
	for(int j=0;j<m;j++)
	if((j>>i)&1) f[j]=Merge(f[j],f[j-(1<<i)]);
	LL ans=X(f[0])+Y(f[0]);
	for(int i=1;i<m;i++)
	{
		ans=max(ans,X(f[i])+Y(f[i]));
		printf("%lld\n",ans);
	}
	return 0;
}

CF1208F Bits And Pieces#

题目传送门
在这里插入图片描述
考虑可以枚举i
位运算的最大值,考虑按位填数
从高到低遍历每一位
判断是不是能找到一个就j,k
使得当前的答案ansj&k,如果能,这一位填1,否则是0
显然j&kans的超集,因此j,k都是ans的超集
而我们只需要找到最靠右的j,k判断是不是大于i就可以了
可以用高维后缀和处理

#include<bits/stdc++.h>
using namespace std;
#define PII pair<int,int>
#define mk(a,b) make_pair(a,b)
#define X(a) a.first
#define Y(a) a.second
const int K = 21;
const int N = 1e6+7;
PII f[(1<<K)+9];
void up(int s,int pos)
{
	if(pos==X(f[s])||pos==Y(f[s])||pos==-1) return;
	if(X(f[s])==-1) X(f[s])=pos;
	else 
	{
		if(pos>X(f[s])) 
		{
			Y(f[s])=X(f[s]);
			X(f[s])=pos;
		}
		else
		{
			if(Y(f[s])==-1) Y(f[s])=pos;
			else Y(f[s])=max(Y(f[s]),pos);
		}
	}
}
int a[N];
int main()
{
	int n;
	for(int i=0;i<(1<<21);i++)
	f[i]=mk(-1,-1);
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		up(a[i],i); 
	}
	for(int i=0;i<21;i++)
	for(int j=0;j<(1<<21);j++)
	if(!((j>>i)&1)) 
	{
		up(j,X(f[j+(1<<i)]));
		up(j,Y(f[j+(1<<i)]));
	}
	int Ans=0;
	for(int i=1;i<=n-2;i++)
	{
		int ans=0;
		for(int p=20;p>=0;p--)
		{
			if((a[i]>>p)&1) continue;
			int s=ans+(1<<p);
			int x=X(f[s]),y=Y(f[s]);
			if(x>i&&y>i) ans=s;
		}
		Ans=max(Ans,ans|a[i]);
	}
	cout<<Ans;
	return 0;
} 

CF165E Compatible Numbers#

题目传送门

在这里插入图片描述
很简单,只需要判断ai的补集的子集是不是全为空就行了

#include<bits/stdc++.h>
using namespace std;
const int K =22;
const int N = 1e6+7;
int f[(1<<K)+9];
void up(int s,int x)
{
	if(!f[s]) f[s]=x;
}
int a[N];
int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		up(a[i],a[i]);
	}
	for(int i=0;i<22;i++)
	for(int j=0;j<(1<<22);j++)
	if((j>>i)&1) up(j,f[j-(1<<i)]);
	for(int i=1;i<=n;i++)
	{
		int s=a[i],t=(((1<<22)-1)^s);
		int j=f[t];
		if(j==0) printf("-1 ");
		else printf("%d ",j);
	}
	return 0;
}

CF383E Vowels#

题目传送门
在这里插入图片描述
直接求不是很好求,考虑容斥
用总的单词个数n,减掉一个元音也没包含的个数
一个元音也没包含,相当于是当前元音集合补集的子集
用高维前缀和就可以处理了

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL K =24;
const LL N = 1e6+7;
LL f[(1<<K)+9];
void up(LL s,LL v)
{
	f[s]=f[s]+v;
}
LL a[N];
char s[50]; 
int main()
{
	LL n;
	cin>>n;
	for(LL i=1;i<=n;i++)
	{
		scanf("%s",s+1);
		LL S=0;
		for(LL j=1;j<=3;j++)
		{
			LL c=s[j]-'a';
			if(s[j]>'x') continue;
			S=S|(1<<c);
		}
		up(S,1);
	}
	for(LL i=0;i<24;i++)
	for(LL j=0;j<(1<<24);j++)
	if((j>>i)&1) up(j,f[j-(1<<i)]);
	LL Ans=0;
	for(LL S=0;S<(1<<24);S++)
	{
		LL T=((1<<24)-1)-S;
		LL cnt=n-f[T];
		Ans=Ans^((LL)cnt*cnt);
	}
	cout<<Ans;
	return 0;
}

CF449D Jzzhu and Numbers#

题目传送门
在这里插入图片描述
f(i)=j=1n[i&aj==i]
可以高维前缀和处理
g(i)为选出一个子序列使得子序列j1,j2
满足iaji&aj2的方案数
那么显然是2f(i)1
我们是h(i)为题目与的值是i的答案,题目所求即为h(0)
那么g(i)=i&j==ih(j)
也就是高维后缀和,我们直接来一个高维后缀差分就可以的到h啦

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+7;
const int K = 20;
const int M = (1<<K)+9;
typedef long long LL;
const int mod = 1e9+7;
LL Pow[N];
LL f[M],g[M];
int main()
{
	int n;
	cin>>n;
	Pow[0]=1;
	for(int i=1;i<=n;i++)
	{
		Pow[i]=Pow[i-1]*2ll%mod;
		LL a;
		scanf("%lld",&a);
		f[a]++;
	}
	for(int i=0;i<20;i++)
	for(int j=0;j<(1<<20);j++)
	if(!((j>>i)&1)) 
	f[j]=(f[j]+f[j+(1<<i)]);
	for(int i=0;i<(1<<20);i++)
	g[i]=(Pow[f[i]]-1+mod)%mod;
	for(int i=0;i<20;i++)
	for(int j=0;j<(1<<20);j++)
	if(!((j>>i)&1)) 
	g[j]=(g[j]-g[j+(1<<i)]+mod)%mod;
	cout<<g[0];
	return 0;
} 
posted @   Larunatrecy  阅读(261)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示
主题色彩