2019.12.29日常总结兼差分与前缀和略讲

前缀和

现在您有一个序列 A 1 , A 2 , A 3 . . . A N A_1,A_2,A_3...A_N ,求 i = l r A i \sum^{r}_{i=l} A_i

我们有 O ( N ) O(N) 的暴力算法,但它并不够完美。我们可以做到 O ( 1 ) O(1)

具体的做法就是定义一个前缀和数组 F F ,其中 F i = j = 1 i A j F_i=\sum^{i}_{j=1} A_j ,那么根据加法的性质,我们可以有 i = l r A i = i = 1 r A i i = 1 l 1 A i = F r F l 1 \sum^{r}_{i=l} A_i=\sum^{r}_{i=1} A_i-\sum^{l-1}_{i=1} A_i=F_r-F_{l-1}

同时,我们可以用 O ( N ) O(N) 的时间复杂度计算 F F ,根据定义,我们有 F i = F i 1 + A i F_i=F_{i-1}+A_i 。以上算法就是前缀和

当然,前缀和算法并不是只能运用于加法,事实上,很多运算都可以用前缀和算法加速。比如我们常见的异或运算(因为一个数异或两次同一个数相当于没有异或)。可以说,有逆运算的运算都可以用前缀和加速(乘法除外)。


差分

差分是前缀和的逆运算。所谓差分,就是定义一个差分数组 D D ,其中, D i = A i A i 1 D_i=A_i-A_{i-1}

差分的第一个基本运算就是利用差分数组还原原数组,这可以用前缀和算法解决。即 A i = j = 1 i D j A_i=\sum_{j=1}^{i} D_j

差分的第二个基本运算是 O ( 1 ) O(1) 解决区间修改。比如把 A l , A l + 1 , A l + 2 . . . A r A_l,A_{l+1},A_{l+2}...A_{r} 都加上一个数 T T 。因为区间同时加上同一个数,所以其内部元素的相对差值并不会改变(即 D l + 1 , D l + 2 . . . D r D_{l+1},D_{l+2}...D_{r} 不变)。事实上,改变的只有 D l , D r + 1 D_l,D_{r+1} ,我们只需 O ( 1 ) O(1) 修改它们即可(D[l]+=T,d[r+1]-=T)。


例题

洛谷P3909

【题意】: 对于 A 1 , A 2 , A 3 . . . A N A_1,A_2,A_3...A_N ,求 6 × i = 1 n j = i + 1 n k = j + 1 n A i × A j × A k 6 \times \sum^n_{i=1} \sum^n_{j=i+1} \sum^n_{k=j+1} A_i \times A_j \times A_k m o d mod 1 0 9 + 7 10^9+7 的值。
【思路】: 原式 = k = 3 n A k × [ j = 2 k 1 A j × ( i = 1 j = 1 A i ) ] =\sum^n_{k=3} A_k\times [\sum^{k-1}_{j=2} A_j \times(\sum^{j=1}_{i=1} A_i)] ,然后记 s [ i ] = j = 1 i A j s[i]=\sum^{i}_{j=1} A_j t [ i ] = j = 1 i A j × s [ j 1 ] t[i]=\sum^{i}_{j=1} A_j\times s[j-1] ,原式 = k = 3 n A k × t [ k 1 ] =\sum^n_{k=3} A_k\times t[k-1] ,我们就可以在 O ( n ) O(n) 的时间复杂度内计算出答案。
【代码】:

#define ll long long
#define gc getchar()
#define g(c) isdigit(c)
inline int read(){
	char c=0;int x=0;bool f=0;
	while (!g(c)) f=c=='-',c=gc;
	while (g(c)) x=x*10+c-48,c=gc;
	return f?-x:x;
}
const int N=1e6+1e3;
ll s[N],t[N],a[N],ans[N],n;
const ll mod=1e9+7;
int main(){
	freopen("t1.in","r",stdin);
	n=read();//ans=0;
	for(int i=1;i<=n;i++){
		a[i]=read();
		s[i]=(s[i-1]+a[i])%mod;
	}
//	s[i]=a[1]+a[2]+a[3]+a[4]+..+a[i]
	for(int i=1;i<=n;i++)
		t[i]=(t[i-1]+a[i]*s[i-1]%mod)%mod;
//	t[i]=a[2]*s[1]+a[3]*s[2]+a[4]*a[3]+...+a[i]*s[i-1]
	for(int i=1;i<=n;i++)
		ans[i]=(ans[i-1]+a[i]*t[i-1]%mod)%mod;
//	ans[i]=a[3]*t[2]+a[4]*t[3]+a[5]*t[4]+...+a[i]*t[i-1]
	cout<<(ans[n]*6)%mod;
	return 0;
}

洛谷P3819

【题意】: 涞坊路是一条长 L L 米的道路,道路上的坐标范围从 0 0 L L ,路上有 N N 座房子,第 i i 座房子建在坐标为 x [ i ] x[i] 的地方,其中住了 r [ i ] r[i] 人。

松江1843路公交车要在这条路上建一个公交站,市政府希望让最多的人得到方便,因此希望所有的每一个的居民,从家到车站的距离的总和最短

公交站应该建在哪里呢?

【思路】: F ( l ) F(l) 表示在第 l l 个单位坐标建公交车时的距离总和,那么 F F 函数是一个单谷函数,我们可以用三分法求出其最小值。

考虑如何在 O ( 1 ) O(1) 的时间复杂度内求出 F ( l ) F(l)

把数轴分为两部分: [ X 1 , l 1 ] [X_1,l-1] [ l + 1 , X N ] [l+1,X_N] 。记 l l 在第 i i 号和第 i + 1 i+1 号居民区之间( i i 可以用二分法求出)。 F ( l ) = j = 1 i 1 ( l X j ) × R j + j = i + 1 n ( X j l ) × R j F(l)=\sum^{i-1}_{j=1} (l-X_j) \times R_j+ \sum^{n}_{j=i+1} (X_j-l) \times R_j

我们可以继续优化,记 s s [ i ] = j = 1 i X j × R j , s r [ i ] = j = 1 i R j ss[i]=\sum^{i}_{j=1} X_j \times R_j,sr[i]=\sum^{i}_{j=1} R_j 。则 F ( l ) = j = i + 1 n X j × R j j = i + 1 n l × R j + j = 1 i l × R j j = 1 i X j × R j = l × s r [ i ] s s [ i ] + ( s s [ n ] s s [ i ] ) l × ( s r [ n ] s r [ i ] ) F(l)=\sum^{n}_{j=i+1} X_j \times R_j-\sum^{n}_{j=i+1} l \times R_j+\sum ^{i}_{j=1} l \times R_j-\sum^{i}_{j=1} X_j \times R_j=l \times sr[i]-ss[i]+(ss[n]-ss[i])-l \times (sr[n]-sr[i])

在这里,笔者采用了定量三分法,循环三分 1000 1000 次。

时间复杂度: O ( 1000 × l o g N + N ) O(1000 \times log_N+N)

【代码】:

#define ll long long
#define gc getchar()
#define g(c) isdigit(c)
inline ll read(){
	char c=0;ll x=0;bool f=0;
	while (!g(c)) f=c=='-',c=gc;
	while (g(c)) x=x*10+c-48,c=gc;
	return f?-x:x;
}
const int N=1e5+1e3;
ll ss[N],sr[N],ans,n,x[N];
inline ll F(ll t){
	int i=upper_bound(x+1,x+n+1,t)-x-1;
//	当前计算的坐标位于第i个居民区和第i+1个居民区之间 
	register ll cnt=0ll;//cnt:所有居民的总贡献 
	cnt+=t*sr[i]-ss[i];//从第1号到第i号居民的总贡献 
	cnt+=(ss[n]-ss[i])-t*(sr[n]-sr[i]);//从第i+1号到第n号居民的总贡献
	return cnt;//现在的cnt即所有居民的总贡献
}
ll l,r,lmid,rmid;
int main(){
	freopen("t1.in","r",stdin);
	read();n=read();ans=0ll;
	for(int i=1;i<=n;i++){
		x[i]=read();ll r=read();
		ss[i]=ss[i-1]+x[i]*r;
		sr[i]=sr[i-1]+r;
	}
	ans=ss[n];l=x[1];r=x[n];
//	printf("l+r=%lld\n",l+r);
	for(int i=1;i<1001;i++){
		lmid=(l+r)/2ll;
		rmid=(lmid+r)/2ll;
		if (F(lmid)>=F(rmid))
			l=lmid;
		else r=rmid;
	}
	for(ll i=l;i<=r;i++)
		ans=min(ans,F(i));
	printf("%lld",ans);
	return 0;
}
发布了82 篇原创文章 · 获赞 4 · 访问量 1771

猜你喜欢

转载自blog.csdn.net/ZHUYINGYE_123456/article/details/103753209