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];
也就是对每一维分别做前缀和
那么高维前缀和呢?
它是解决以下问题的
例题#
已知一个序列,对于每一个
求
也就是求它的子集的权值和
直接子集枚举做是的,我们需要更快的算法
考虑刚才前缀和的过程,发现对于二进制而言,它的子集一定是它的前缀和中的
因为一定会有某一位比这个数小,所以是前缀和
但是我们不可能按照容斥做法做前缀和,因此就对每一维做前缀和
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)];
高维差分#
因为差分和前缀和是逆运算,所以就有了下面的代码
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#
题目传送门
注意到我们只需要求出
的最大值,取个前缀max就行了
但是这样也不好求,我们考虑继续转化为
也就是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
使得当前的答案,如果能,这一位填1,否则是0
显然是的超集,因此都是的超集
而我们只需要找到最靠右的判断是不是大于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#
#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#
题目传送门
设
可以高维前缀和处理
设为选出一个子序列使得子序列
满足的方案数
那么显然是
我们是为题目与的值是i的答案,题目所求即为
那么
也就是高维后缀和,我们直接来一个高维后缀差分就可以的到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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】