The Trip On Abandoned Railway题解 二次差分树状数组

题目链接传送门

题目描述

题目大意是,一条废弃的地铁路线上有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≤i d[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);
            }
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章