[学习笔记] 高维前缀和

前言

怎么这么基础的东西我到快退役的时候才开始学,虽然我现在学东西还是挺快的

下面是参考资料,当然本篇博客加入了自己的理解,话说我搞得这么正式干嘛

原理

设高维空间 \(U\) 一共有 \(D\) 维,需要对 \(f\) 求出高维前缀和 \(sum\),设 \(dp[i][s]\) 表示同 \(s\)\(D-i\) 维相同的所有点对于 \(s\) 点的贡献。首先我们可以知道 \(dp[0][s]=f[s],dp[D][s]=sum[s]\)

有转移:\(dp[i][s]=dp[i-1][s]+dp[i][s']\),其中 \(s'\) 表示第 \(i\) 维恰好比 \(s\)\(1\) 的点。为什么会存在这么简洁的递推关系呢?从贡献法的角度思考,对于点 \((p_1,p_2...p_D)\) 只会在最后一个 \(p_k<q_k\) 贡献给点 \((q_1,q_2...q_D)\),这是因为如果不是最后一个,那么 \(s'\) 是不会包含点 \(p\) 的,如果是最后一个 \(s'\) 会包含点 \(p\),这样每个点都恰好贡献了一次,就不需要复杂的容斥原理了。

时间复杂度 \(O(D\cdot |U|)\),大部分情况下非常优秀,且代码实现非常简洁。

Dirichlet 前缀和

点此看题

这是高维前缀和一个经典应用,我们把数 \(x\) 做唯一分解得到 \(x=p_1^{x_1}\cdot p_2^{x_2}...p_{k}^{x_k}\),那么 \(x\) 可以贡献给 \(y\) 的充要条件是 \(\forall i,x_i\leq y_i\),这启示我们可以把质数的次幂当成坐标来做高维前缀和。

那么 \(s'\) 的求法就是除以当前这一维的质数,根据质数分布及调和级数的理论,时间复杂度 \(O(n\log \log n)\)

#include <cstdio>
#define uint unsigned int
const int M = 20000005;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,cnt,p[M],vis[M];uint seed,a[M],ans;
uint getnext()
{
	seed^=seed<<13;
	seed^=seed>>17;
	seed^=seed<<5;
	return seed;
}
void init(int n)
{
	for(int i=2;i<=n;i++)
	{
		if(!vis[i]) p[++cnt]=i;
		for(int j=1;j<=cnt && i*p[j]<=n;j++)
		{
			vis[i*p[j]]=1;
			if(i%p[j]==0) break;
		}
	}
}
signed main()
{
	n=read();scanf("%u",&seed);init(n);
	for(int i=1;i<=n;i++)
		a[i]=getnext();
	for(int i=1;i<=cnt;i++)
		for(int j=1;j*p[i]<=n;j++)
			a[j*p[i]]+=a[j];
	for(int i=1;i<=n;i++)
		ans^=a[i];
	printf("%u\n",ans);
}

Present for Vitalik the Philatelist

题目描述

点此看题

解法

\(s_i\) 表示 \(\gcd=i\) 的集合个数,设 \(g_i\) 表示和 \(i\) 的互质的 \(a_x\) 个数,那么答案可以写成:

\[\sum_{i=2}^{m}s_i\cdot g_i \]

\[\begin{aligned} g(i)&=\sum_{x=1}^n[(a_x,i)=1]\\ g(i)&=\sum_{x=1}^n\sum_{d|(a_x,i)}\mu(d)\\ g(i)&=\sum_{d|i}\mu(d)\cdot c_{d} \end{aligned} \]

\(c_i\) 表示是 \(i\) 倍数的 \(a_x\) 个数,可以做高维后缀和得到(只需要改一下求和的方向即可)。

\(g'_i=\mu(i)\cdot c_i\),那么它做高维前缀和可以得到 \(g_i\)

那么现在的问题是计算 \(s_i\),设 \(s_i'\) 表示 \(\gcd\)\(i\) 倍数的集合个数,显然 \(s_i'=2^{c_i}-1\),并且有:

\[s_i'=\sum_{d|i}s_d \]

那么对 \(s_i'\)逆高维前缀和就可以得到 \(s_i\) 了,把高维前缀和的过程反过来即可。

时间复杂度 \(O(m\log \log m)\),其中 \(m\) 表示值域大小。

#include <cstdio>
#include <iostream>
using namespace std;
const int M = 10000005;
const int MOD = 1e9+7;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,cnt,ans,p[M],mu[M];bool vis[M];
int c[M],g[M],s[M],pw[M];
void init()
{
	mu[1]=1;
	for(int i=2;i<=m;i++)
	{
		if(!vis[i]) p[++cnt]=i,mu[i]=-1;
		for(int j=1;j<=cnt && i*p[j]<=m;j++)
		{
			vis[i*p[j]]=1;
			if(i%p[j]==0) break;
			mu[i*p[j]]=-mu[i];
		}
	}
}
signed main()
{
	n=read();pw[0]=1;
	for(int i=1;i<=n;i++)
	{
		int x=read();
		m=max(m,x);c[x]++;
		pw[i]=pw[i-1]*2%MOD;
	}
	init();
	//c
	for(int i=1;i<=cnt;i++)
		for(int j=m/p[i];j;j--)
			c[j]+=c[j*p[i]];
	//g
	for(int i=1;i<=m;i++) g[i]=c[i]*mu[i];
	for(int i=1;i<=cnt;i++)
		for(int j=1;j*p[i]<=m;j++)
			g[j*p[i]]+=g[j];
	//s
	for(int i=1;i<=m;i++) s[i]=pw[c[i]]-1;
	for(int i=cnt;i>=1;i--)
		for(int j=1;j*p[i]<=m;j++)
			s[j]=(s[j]-s[j*p[i]]+MOD)%MOD;
	//ans
	for(int i=2;i<=m;i++)
		ans=(ans+1ll*s[i]*g[i])%MOD;
	printf("%d\n",ans);
}

Bits And Pieces

题目描述

点此看题

解法

考虑枚举 \(a_i\),然后对于 \(a_i\) 的超集从大到小地考虑。

那么现在的问题是,是否能通过位置 \(>i\)\(a_x\) 组成某个数字 \(s\),那么我们可以考虑 \(s\) 的超集,找出其中位置最大的两个数。可以考虑高维后缀和,只是这里加法的定义改成了 pair 的取 \(\max\)

#include <cstdio>
#include <iostream>
using namespace std;
const int M = 2100005;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,ans,a[M],mx[M],cx[M];
void add(int x,int y)
{
	if(mx[x]<y) cx[x]=mx[x],mx[x]=y;
	else if(cx[x]<y) cx[x]=y;
}
signed main()
{
	n=read();m=(1<<21)-1;
	for(int i=1;i<=n;i++) a[i]=read();
	for(int i=0;i<=m;i++) mx[i]=cx[i]=-1;
	for(int i=1;i<=n;i++) add(a[i],i);
	for(int i=0;i<21;i++)
		for(int s=m;s>=0;s--) if(s>>i&1)
		{
			add(s^(1<<i),mx[s]);
			add(s^(1<<i),cx[s]);
		}
	for(int i=1;i<n-1;i++)
	{
		int res=0,lim=m^a[i];
		for(int j=20;j>=0;j--) if(lim>>j&1)
			if(cx[res^(1<<j)]>i) res^=(1<<j);
		ans=max(ans,res|a[i]);
	}
	printf("%d\n",ans);
}
posted @ 2022-03-07 17:04  C202044zxy  阅读(481)  评论(0编辑  收藏  举报