2019.03.06【BJOI2018】【洛谷P4458】【BZOJ5291】链上二次求和(线段树)

洛谷传送门

BZOJ传送门


解析:

直接来看我们要求的式子,设 S S S是点权的前缀和, S S SS SS S S S的前缀和

一个询问 l , r l,r l,r表示要求的是
A n s = ∑ k = l r ∑ i = k n ( S i − S i − k ) = ∑ k = l r ( S S n − S S k − 1 − S S n − k ) = ( r − l + 1 ) S S n − ∑ k = l − 1 r − 1 S S k − ∑ k = n − r n − l S S k \begin{aligned} Ans&=&&\sum_{k=l}^r\sum_{i=k}^n(S_i-S_{i-k})\\ &=&&\sum_{k=l}^r(SS_n-SS_{k-1}-SS_{n-k})\\ &=&&(r-l+1)SS_n-\sum_{k=l-1}^{r-1}SS_{k}-\sum_{k=n-r}^{n-l}SS_k \end{aligned} Ans===k=lri=kn(SiSik)k=lr(SSnSSk1SSnk)(rl+1)SSnk=l1r1SSkk=nrnlSSk

实际上就是询问 S S SS SS的区间和。

我们考虑一个修改 ( l , r , v ) (l,r,v) (l,r,v)会对数组 s s ss ss产生什么影响。

我们知道,前缀和的逆运算是差分,所以实际上我们是在 S S SS SS的二阶差分数组上面修改,而一个 0 0 0次的修改,做两次前缀和,影响是。。。二次函数。

手推一下可以得到,对于 l ≤ i ≤ r l\leq i\leq r lir,影响是: S S i + = ( i − l + 1 ) ( i − l + 2 ) v 2 SS_i+=\frac{(i-l+1)(i-l+2)v}{2} SSi+=2(il+1)(il+2)v

而对于后面的数 r &lt; i ≤ n r&lt;i\leq n r<in,由于差分的影响达不到这里,所以这里的变换是一次的,为:

S S i + = l e n ( l e n + 1 ) v 2 + l e n ( i − r ) v SS_i+=\frac{len(len+1)v}{2}+len(i-r)v SSi+=2len(len+1)v+len(ir)v

其中 l e n = r − l + 1 len=r-l+1 len=rl+1

线段树维护区间加一个与下标有关的二次函数就可以了。


代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc get_char
#define cs const

namespace IO{
	inline char get_char(){
		static cs int Rlen=1<<20|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	
	inline int getint(){
		re char c;
		while(!isdigit(c=gc()));re int num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return num;
	}
}
using namespace IO;

cs int mod=1e9+7,inv2=(mod+1)/2;
inline int add(cs int &a,cs int &b){return a+b>=mod?a+b-mod:a+b;}
inline int dec(cs int &a,cs int &b){return a<b?a-b+mod:a-b;}
inline int mul(cs int &a,cs int &b){return (ll)a*b%mod;}


cs int N=200005;
int n,m;
int a[N<<2],b[N<<2],c[N<<2];
int sum[N<<2];
int d1[N],d2[N];

inline void pushup(cs int &k){sum[k]=add(sum[k<<1],sum[k<<1|1]);}
inline void pushadd(cs int &k,cs int &l,cs int &r,cs int &da,cs int &db,cs int &dc){
	a[k]=add(a[k],da),b[k]=add(b[k],db),c[k]=add(c[k],dc);
	sum[k]=add(sum[k],add(add(mul(da,dec(d2[r],d2[l-1])),mul(db,dec(d1[r],d1[l-1]))),mul(r-l+1,dc)));
}
inline void pushdown(cs int &k,cs int &l,cs int &r){
	if(a[k]||b[k]||c[k]){
		int mid=(l+r)>>1;
		pushadd(k<<1,l,mid,a[k],b[k],c[k]);
		pushadd(k<<1|1,mid+1,r,a[k],b[k],c[k]);
		a[k]=b[k]=c[k]=0;
	}
}

int ss[N];
inline void build(int k,cs int &l,cs int &r){
	if(l==r){
		sum[k]=ss[l];
		return ;
	}
	int mid=(l+r)>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
	pushup(k);
}

inline void modify(int k,cs int &l,cs int &r,cs int &ql,cs int &qr,cs int &a,cs int &b,cs int &c){
	if(ql<=l&&r<=qr)return pushadd(k,l,r,a,b,c);
	int mid=(l+r)>>1;
	pushdown(k,l,r);
	if(ql<=mid)modify(k<<1,l,mid,ql,qr,a,b,c);
	if(mid<qr)modify(k<<1|1,mid+1,r,ql,qr,a,b,c);
	pushup(k);
}

inline int query(int k,cs int &l,cs int &r,cs int &ql,cs int &qr){
	if(ql<=l&&r<=qr)return sum[k];
	pushdown(k,l,r);
	int mid=(l+r)>>1;
	if(qr<=mid)return query(k<<1,l,mid,ql,qr);
	if(mid<ql)return query(k<<1|1,mid+1,r,ql,qr);
	return add(query(k<<1,l,mid,ql,qr),query(k<<1|1,mid+1,r,ql,qr));
}

signed main(){
	n=getint(),m=getint();
	for(int re i=1;i<=n;++i)ss[i]=add(getint(),ss[i-1]);
	for(int re i=1;i<=n;++i)ss[i]=add(ss[i],ss[i-1]);
	for(int re i=1;i<=n;++i)d1[i]=add(d1[i-1],i),d2[i]=add(d2[i-1],mul(i,i));
	build(1,0,n);
	int l,r,v,a,b,c,len;
	while(m--){
		switch(getint()){
			case 1:{
				l=getint(),r=getint();
				if(l>r)swap(l,r);
				v=getint();
				a=inv2;
				b=mul(dec(3,add(l,l)),inv2);
				c=mul(mul(l-1,l-2),inv2);
				modify(1,0,n,l,r,mul(a,v),mul(b,v),mul(c,v));
				if(r==n)break;
				len=r-l+1;
				modify(1,0,n,r+1,n,0,mul(r-l+1,v),mul(v,dec(mul(inv2,mul(len,len+1)),mul(len,r))));
				break;
			}
			case 2:{
				l=getint(),r=getint();
				cout<<dec(mul(query(1,0,n,n,n),r-l+1),add(query(1,0,n,l-1,r-1),query(1,0,n,n-r,n-l)))<<"\n";
				break;
			}
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值