Loading

差分前缀和及其扩展与优化

很没用的总结,如果您发现了错误,欢迎指正!!

一维一阶前缀和

对于一个数组 \(A\),设其前缀和数组为 \(sum\)

\(sum_i\) 的意义是 \(\sum\limits_{k=1}^iA_k\),即区间 \([1,i]\) 所有数的和

显然对于不带修改的区间求和,我们可以用前缀和数组 \(O(1)\) 查询

\(\sum\limits_{i=l}^rA_i=sum_{r}-sum_{l-1}\)

构造

构造前缀和数组:\(sum_i=sum_{i-1}+A_i\)

一维一阶差分

对于一个数组 \(A\),设其差分数组为 \(dif\)

\(dif_i\) 的意义是 \(A_i-A_{i-1}\),即相邻两个数的差

对于单点或区间加减法,如果最后只是求修改完毕后的数列,那么我们可以 \(O(1)\) 修改,最后 \(O(n)\) 还原

\([l,r]+add\Rightarrow dif_l+add,dif_{r+1}-add\)

最后 \(A_i=\sum\limits_{k=1}^idif_k\)

构造

构造差分数组:\(dif_i=A_i-A_{i-1}\)

高维一阶前缀和

对于一个高维数组 \(A\),设其前缀和数组为 \(sum\)

\(sum_{i_1,\cdots,i_k}\) 的意义是区间 \((1,\cdots,1)\sim(i_1,\cdots,i_k)\) 内所有数的和

利用容斥原理,即可对不带修改的高维区间求和询问 \(O(1)\) 回答

构造

\(k\) 维前缀和即利用 \(k\) 重循环直接加法计算

\(sum_{i_1,\cdots,i_k}=sum_{i_1,\cdots,i_{k-1}}+A_{i_1,\cdots,i_k}\)

高维一阶差分

对于一个高维数组 \(A\),设其差分数组为 \(dif\)

对于一个 \(k\) 维区间加法,我们要对其区间开端 \(+1\),区间末端 \(-1\)

例如 \(2\) 维区间加法 \((x,y)\sim(u,w)\)

显然 \(dif_{x,y}+1,dif_{u+1,y}-1,dif_{x,w+1}-1,dif_{u+1,w+1}+1\)

推广到一般即为高维的情况

构造

一维高阶前缀和

高阶前缀和具有组合意义

对于一个数组 \(A\),设其 \(k\) 阶前缀和数组为 \(sum_k\)

其一阶前缀和 \(sum_{1,i}=sum_{1,i-1}+A_i\)

对数组 \(sum_{k-1}\) 再做前缀和,得到的前缀和数组即为其 \(k\) 阶前缀和数组 \(sum_k\)

构造

构造一维高阶前缀和数组:

\[\begin{cases} sum_{k,i}=sum_{k,i-1}+sum_{k-1,i}&(k>1) \\ sum_{k,i}=sum_{k,i-1}+A_i&(k=1) \end{cases} \]

一维高阶差分

高阶差分具有组合意义

对于一个数组 \(A\),设其 \(k\) 阶差分数组为 \(dif_k\)

其一阶差分 \(dif_i=A_i-A_{i-1}\)

对数组 \(dif_{k-1}\) 再做差分,得到的差分数组即为其 \(k\) 阶差分数组 \(dif_k\)

构造

\[\begin{cases} dif_{k,i}=dif_{k-1,i}-dif_{k-1,i-1}&(k>1) \\ dif_{k,i}=A_i-A_{i-1}&(k=1) \end{cases} \]

生成函数优化

本处讨论一维高阶的情况

设序列 \(a\) 的普通生成函数 \(f(x)\)\(\sum\limits^{\infty}_{n=0}a_nx^n\)\(g(x)=\sum\limits^\infty_{n=0}b_nx^n\) 是另一个新的数列 \(\{b_n\}^\infty_{n=0}\) 的生成函数

高阶前缀和

首先写出前缀和的计算方法:\(sum_i=\sum\limits_{k=1}^ia_k\)

熟知 \(f(x)g(x)\) 是数列 \(\left\{\sum\limits^n_{k=0}a_kb_{n-k}\right\}_{n=0}^\infty\) 的生成函数 (性质1)

\(b\) 的每一项 \(b_i\) 都等于 \(1\) 时,\(\dfrac{f(x)}{1-x}\) 即为其一阶前缀和

同理,二阶前缀和是在一阶前缀和的基础上得到的,\(k\) 阶前缀和是在 \(k-1\) 阶前缀和的基础上得到的,我们用 \(\dfrac{1}{1-x}\) 不断相乘即可

所以 \(k\) 阶前缀和即为 \(f\times\dfrac{1}{(1-x)^k}\)

根据上文性质1,\(\dfrac{1}{(1-x)^k}\) 即为 \(\left\{\sum\limits_{n_1+n_2+\dots+n_k=n}1\right\}_{n=0}^\infty\) 的OGF

考虑组合意义,即 \(\left\{\dbinom{n+k-1}{n}\right\}_{n=0}^\infty\) 的OGF

递推组合数:

\(\dbinom{i+k-1}{i}=\dbinom{i+k-2}{i-1}\dfrac{k+i-1}{i}\)

然后跟 \(f\) NTT卷一下就可以

高阶差分

首先写出差分的计算方法:\(dif_i=a_i-a_{i-1}\)

依然考虑性质1,如果 \(b_0=1,b_1=-1,b_i=0\;(i>1)\)

此时 \(f\times g=\sum\limits_{n=0}^\infty\left(\sum\limits^n_{k=0}a_kb_{n-k}\right)x^n=\sum\limits_{n=0}^\infty(a_n-a_{n-1})x^n\)

显然我们依然能将差分数组的计算方法写成生成函数相乘的形式

此时数列 \(b\) 即为 \(1-x\)

所以 \(k\) 阶差分即为 \(f\times(1-x)^k\)

对于 \((1-x)^k\) 直接二项式定理拆开

\((1-x)^k\) 的每项系数是 \(\sum\limits_{i=0}^k(-1)^i\dbinom{k}{i}\)

递推组合数:

\(\dbinom{k}{i}=\dbinom{k}{i-1}\dfrac{k-i+1}{i}\)

然后拿 \(f\) NTT卷一下就可以

是不是非常非常简单?

放放代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define N 1000001
#define INF 1100000000
#define Kafuu return
#define Chino 0
#define fx(l,n) inline l n
#define set(l,n,ty,len) memset(l,n,sizeof(ty)*len)
#define cpy(f,t,ty,len) memcpy(t,f,sizeof(ty)*len)
#define R register int
#define int long long
using namespace std;
const int mod=1004535809,pr=3;
int n,k,t,ori[N],invp,invx,x=1,br[N],prp[N],binom[N],T[N];
fx(int,gi)(){
	char C=getchar();long long n=0,f=1;
	while(C<'0'||C>'9'){
		if(C=='-') f=-f;
		C=getchar();
	}
	while(C>='0'&&C<='9') ((n*=10)+=C-'0')%=mod,C=getchar();
	return (n*f)%mod;
}
fx(int,pow)(int a,int b=mod-2){
	int sum=1;
	while(b){
		if(b&1) (sum*=a)%=mod;
		(a*=a)%=mod;
		b>>=1;
	}
	return sum;
}
fx(void,NTT)(int *f,short r){
	R len,hl,exp,uni,st,i;
	for(i=0;i<x;i++) if(i<br[i]) swap(f[i],f[br[i]]);
	for(len=2,hl=1;len<=x;hl=len,len<<=1){
		exp=pow(r==1?pr:invp,(mod-1)/len);
		for(i=1;i<hl;i++) prp[i]=prp[i-1]*exp%mod;
		for(st=0;st<x;st+=len){
			for(i=0;i<hl;i++){
				uni=prp[i]*f[i|st|hl]%mod;
				f[i|st|hl]=(f[i|st]-uni+mod)%mod;
				f[i|st]=(f[i|st]+uni)%mod;
			}
		}
	}
}
signed main(){
	n=gi(),k=gi(),t=gi();
	for(R i=0;i<n;i++) T[i]=ori[i]=gi();
	while(x<=n+n) x<<=1;
	for(R i=0;i<x;i++) br[i]=(br[i>>1]>>1)|((i&1)?x>>1:0);
	invx=pow(x);invp=pow(pr);binom[0]=1;
	if(!t) for(int i=1;i<=n;i++) binom[i]=binom[i-1]*(k+i-1)%mod*pow(i)%mod;
	else for(int i=1;i<=n;i++) binom[i]=-binom[i-1]*(k-i+1)%mod*pow(i)%mod;
	prp[0]=1;NTT(ori,1);NTT(binom,1);
	for(int i=0;i<x;i++) (ori[i]*=binom[i])%=mod;
	NTT(ori,-1);
	for(int i=0;i<n;i++) cout<<ori[i]*invx%mod<<" ";
}

树上前缀和

树上前缀和即是快速回答两点间距离的一种手段,\(sum\) 数组表示当前点到根结点的距离

于是两点间距离即为 \(sum_x+sum_y-sum_{lca}\)

构造

\(sum_{x}=sum_{fa}+v_{fa\to x}\)

树上差分

对点差分

即为路径上的点同加一个值 \(val\)

对于每个点\(p\),设 \(S\) 为其子结点集合,\(dif_p=\sum\limits_{t\in S}dif_t\)

注意自下向上有序统计,不要随便找一个点套用上边的式子

构造

\(dif_x+1,dif_y+1,dif_{lca}-1,dif_{lcafa}-1\)

对边差分

即为路径上的边同加一个值 \(val\)

\(dif_p\) 表示边 \(fa\to p\)

统计同对点差分

构造

\(dif_x+1,dif_y+1,dif_{lca}-2\)


你已经看完啦,相信你已经理解并精通了前缀和与差分,快去研究高维高阶前缀和&高维高阶差分吧!

posted @ 2021-03-01 20:22  zythonc  阅读(290)  评论(1编辑  收藏  举报