题目链接传送门
题目描述
题目大意是,一条废弃的地铁路线上有n个火车站,第i个火车站初始盘踞着ai个鬼鬼。
然后有一辆幽灵列车会突然从某个车站出现,并从车上下来x个鬼。即该车站鬼魂数量+x。列车会一路行驶直至终点,每经过一个车站,从列车上下来的鬼魂数量就比前一个车站下来的多d个
即从车上下来x+k*d个鬼魂,其中k为从列车出现经过的车站数。
另有一个驱魔师,到了某个车站后,就将该车站的所有鬼魂全部超度,即,将该车站鬼魂数置为0。
操作1:幽灵列车在x站出现,并下来y个鬼魂。(后续每经过一个车站就多d个)
操作2:驱魔师前往x站,输出该站鬼魂数后,将该站鬼魂数清零。
解题思路
采用二次差分树状数组
时间复杂度分析
操作1是区间修改,时间复杂度是o(n),操作2是单点查询,单点修改,时间复杂度是o(1),有m个操作。故时间复杂度为o(m*n)。
十组数据,n,m都是105 的数量级,直接计算数组会超时。
区间修改,单点查询,单点修改,这道题显然要用树状数组或线段树来做。
树状数组或线段树可以将区间修改和查询转为logn的时间复杂度,这样总体的时间复杂度就是o(m*logn)
这里我们选择代码量少的树状数组。
再往下看时请确保你有树状数组的基础,如果你还不懂树状数组如何区间修改和查询的,这有一篇写的不错的博客——传送门
树状数组解决区间修改问题,通常是用差分数组。
一次差分只能解决区间内统一增加k的修改
设车站鬼魂数组为
a[i]
则差分数组d[i]=a[i]-a[i-1]
则a[i]
=∑0<j≤id[j]
——①
但是我们发现对于操作1来讲,除d[x]+=y
外,d[x+1]
至d[n]
都要自增d。——②
又是一个区间修改,但此时的区间修改变成了统一增加d。
这样,操作1就可以转化为对d[]的区间修改(②),对a[i]
的查询可以转换为对d[]
的区间查询(①)。而实现这两种功能,只需再对d[]
进行差分就好。
设二重差分数组k[i]=d[i]-d[i-1]
我们只需针对k[i]
和i*k[i]
建树即可。
做个清晰的对比,来搞清楚操作1和操作2到底做了什么事情。
操作1:
下标:x | x+1 | x+2 |
---|---|---|
a[x]+y | a[x+1]+y+d | a[i+2]+y+2d |
d[x]+y | d[x+1]+d | d[x+2]+d |
k[x]+y | k[x+1]+d-y | k[x+2] |
即
add(x,y);
add(x,d-y);
void add(int i,ll x){
ll ix=i*x;
while(i<=n){
c1[i]=c1[i]+x;
c2[i]=c2[i]+ix;
i+=lowbit(i);
}
}
其中,c1代表k[]的树状数组,c2代表i*k[i]的树状数组
操作2:
查询a[i]
a[i]=∑0<j≤i d[j]
= k[1] ,+ k[1] + k[2], + k[1] + k[2] + k[3] + … + k[1] + k[2] + k[3] + … + k[i]
= Σ(i - j + 1) * k[j] (j从1到 i )
= ∑1≤j≤i (i+1)*k[j] - ∑1≤j≤i j * k[j]
ll getSum(int i){
int k=i;
ll s1=0,s2=0;
while(i>0){
s1=s1+c1[i];
s2=s2+c2[i];
i-=lowbit(i);
}
return (k+1)*s1-s2;
}
a[x]清零
下标:x | x+1 | x+2 |
---|---|---|
a[x]-a[x] | a[x+1] | a[x+2] |
d[x]-a[x] | d[x+1]+a[x] | d[x+2] |
k[x]-a[x] | k[x]+2*a[x] | k[x+2]-a[x] |
ll ans=getSum(x);
add(x,-ans);
add(x+1,2*ans);
add(x+2,-ans);
另一个关于取模的问题。
我第一次提交的时候没有通过全部的样例。
原因是因为我在计算差分数组的时候取模了。
后来想了想,差分数组反映的是差值,是斜率,是数据的变化。如果取模则会影响到计算,从而出错。所以差分数组不取模,最后的结果取模就行。
上代码
#include <iostream>
using namespace std;
const int maxn=1e5+5;
const int mod=1e9+7;
typedef long long ll;
ll a[maxn];
ll c1[maxn]; //维护k[i]二重差分前缀和的树状数组
ll c2[maxn]; //维护i*k[i]二重差分前缀和的树状数组
int n,m,d;
int lowbit(int x){
return x&(-x);
}
void add(int i,ll x){
ll ix=i*x;
while(i<=n){
c1[i]=c1[i]+x;
c2[i]=c2[i]+ix;
i+=lowbit(i);
}
}
ll getSum(int i){
//a[i]=sum(d[i])=sum(k[i])*(n+1)-sum(i*k[i])
int k=i;
ll s1=0,s2=0;
while(i>0){
s1=s1+c1[i];
s2=s2+c2[i];
i-=lowbit(i);
}
return (k+1)*s1-s2;
}
int dd[maxn];
int main(){
int T;
cin>>T;
while(T--){
cin>>n>>m>>d;
//数组qingling
for(int i=0;i<=n;i++)c1[i]=c2[i]=0;
for(int i=1;i<=n;i++){
cin>>a[i];
//计算一层差分
dd[i]=a[i]-a[i-1];
//计算二层差分
add(i,dd[i]-dd[i-1]);
}
int op,x,y;
while(m--){
cin>>op;
if(op==1){
cin>>x>>y;
add(x,y);
add(x+1,d-y);
}
else{
cin>>x;
ll ans=getSum(x);
cout<<(ans+mod)%mod<<endl;
add(x,-ans);
add(x+1,2*ans);
add(x+2,-ans);
}
}
}
}