[BJOI2018] 链上二次求和
一、题目
二、解法
把答案转化成前缀和的形式,设 \(s_i\) 表示原数组的一阶前缀和,那么答案是:
\[\sum_{i=l}^{r}\sum_{j=i}^n s_j-s_{j-i}
\]
设 \(ss_i\) 表示原数组的二阶前缀和(也就是对前缀和再做一次前缀和),答案可以写成:
\[\sum_{i=l}^{r} ss_n-ss_{n-i}-ss_{i-1}
\]
所以我们只需要拿一棵线段树维护原数组的二阶前缀和即可。考虑区间修改 \([l,r]\) 对于二阶前缀和的影响,通过贡献法可以计算出变化为(设 \(G(n)=\frac{n(n+1)}{2}\)):
- 如果 \(i\in[l,r]\),\(ss_i\) 会增加 \(G(i-l+1)\cdot d\)
- 如果 \(i\in(r,n]\),\(ss_i\) 会增加 \([G(r-l+1)+(i-r)\cdot (r-l+1)]\cdot d\)
令 \(ss_i=a_i\cdot i^2+b_i\cdot i+c_i\),在线段树上维护这三项即可,所有修改操作都可以转化成对于这三项的修改。
比如第一种操作,\(G(i-l+1)=\frac{(i-l+1)(i-l+2)}{2}\),那么 \(a_i=\frac{1}{2},b_i=\frac{3-2l}{2},c_i=\frac{l^2-3l+2}{2}\)
时间复杂度 \(O(n\log n)\)
#include <cstdio>
#include <iostream>
using namespace std;
const int M = 800005;
#define int long long
const int MOD = 1e9+7;
const int inv2 = (MOD+1)/2;
const int inv6 = 166666668;
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,s[M],a[M],b[M],c[M],t[M];
void add(int &x,int y) {x=(x+y)%MOD;}
int s1(int n) {return n*(n+1)/2%MOD;}
int s2(int n) {return n*(n+1)%MOD*(2*n+1)%MOD*inv6%MOD;}
void get(int i,int l,int r,int da,int db,int dc)
{
add(a[i],da);add(b[i],db);add(c[i],dc);
add(t[i],da*(s2(r)-s2(l-1))+db*(s1(r)-s1(l-1))+dc*(r-l+1));
}
void down(int i,int l,int r)
{
if(!a[i] && !b[i] && !c[i]) return ;
int mid=(l+r)>>1;
get(i<<1,l,mid,a[i],b[i],c[i]);
get(i<<1|1,mid+1,r,a[i],b[i],c[i]);
a[i]=b[i]=c[i]=0;
}
void build(int i,int l,int r)
{
if(l==r) {t[i]=s[l];return ;}
int mid=(l+r)>>1;
build(i<<1,l,mid);
build(i<<1|1,mid+1,r);
t[i]=(t[i<<1]+t[i<<1|1])%MOD;
}
void upd(int i,int l,int r,int L,int R,int a,int b,int c)
{
if(L>r || l>R) return ;
if(L<=l && r<=R) {get(i,l,r,a,b,c);return ;}
int mid=(l+r)>>1;down(i,l,r);
upd(i<<1,l,mid,L,R,a,b,c);
upd(i<<1|1,mid+1,r,L,R,a,b,c);
t[i]=(t[i<<1]+t[i<<1|1])%MOD;
}
int ask(int i,int l,int r,int L,int R)
{
if(L>r || l>R) return 0;
if(L<=l && r<=R) return t[i];
int mid=(l+r)>>1;down(i,l,r);
return ask(i<<1,l,mid,L,R)+ask(i<<1|1,mid+1,r,L,R);
}
void Upd(int l,int r,int d)
{
int len=r-l+1;
upd(1,0,n,l,r,d*inv2%MOD,(3-2*l)*d%MOD*inv2%MOD,
(l*l-3*l+2)%MOD*d%MOD*inv2%MOD);
if(r<n) upd(1,0,n,r+1,n,0,len*d%MOD,(s1(len)-len*r)%MOD*d%MOD);
}
int Ask(int l,int r)
{
return ((ask(1,0,n,n,n)%MOD*(r-l+1)-ask(1,0,n,l-1,r-1)
-ask(1,0,n,n-r,n-l))%MOD+MOD)%MOD;
}
signed main()
{
n=read();m=read();
for(int i=1;i<=n;i++)
s[i]=(s[i-1]+read())%MOD;
for(int i=1;i<=n;i++)
s[i]=(s[i-1]+s[i])%MOD;
build(1,0,n);
while(m--)
{
int op=read(),l=read(),r=read();
if(l>r) swap(l,r);
if(op==1) Upd(l,r,read());
else printf("%lld\n",Ask(l,r));
}
}