洛 谷 P 1438 无 聊 的 数 列 \color{green}{洛谷P1438 \ \ \ \ \ \ 无聊的数列} 洛谷P1438 无聊的数列
【
题
意
】
:
\color{blue}{【题意】:}
【题意】:
维护一个数列
a
{a}
a,支持两种操作:
1、1 L R K D
:给出一个长度等于
R
−
L
+
1
R-L+1
R−L+1的等差数列,首项为
K
K
K,公差为
D
D
D,并将它对应加到
a
[
L
]
−
a
[
R
]
a[L]-a[R]
a[L]−a[R]的每一个数上。即:令
a
[
L
]
=
a
[
L
]
+
K
,
a
[
L
+
1
]
=
a
[
L
+
1
]
+
K
+
D
,
a
[
L
+
2
]
=
a
[
L
+
2
]
+
K
+
2
×
D
…
…
a
[
R
]
=
a
[
R
]
+
K
+
(
R
−
L
)
×
D
a[L]=a[L]+K,a[L+1]=a[L+1]+K+D,a[L+2]=a[L+2]+K+2\times D……a[R]=a[R]+K+(R-L)\times D
a[L]=a[L]+K,a[L+1]=a[L+1]+K+D,a[L+2]=a[L+2]+K+2×D……a[R]=a[R]+K+(R−L)×D。
2、2 P
:询问序列的第
P
P
P个数的值
a
[
P
]
a[P]
a[P]。
【
思
路
】
:
\color{blue}{【思路】:}
【思路】: 我们把
a
a
a数组进行差分得到
c
c
c,我们考虑每一次1操作
对
c
c
c数组有什么影响。
我们可以发现,每一次的1操作
会让c[l]+=f,c[l+1..r]+=d,f[r+1]+=-f-(r-l)*d
(
f
f
f表示首项,
d
d
d表示公差)。
举个例子:
下标: 1 2...l l+1 l+2 l+3... r r+1 r+2...
a数组增量:+0 +0 +f +f+d +f+2*d +f+3*d +f+(r-l)*d +0 +0
c数组增量:+0 +0 +f +d +d +d +d -f-(r-l)*d +0
从这个表格中我们可以看到一些规律:我们发现直接差分需要将区间 [ l + 1 , r ] [l+1,r] [l+1,r]的 c c c都增加 d d d,所以我们可以联想到 线 段 树 \color{red}{线段树} 线段树。
讲讲线段树的实现。其实大家对线段树的基本概念都有了解(当默认),重点讲讲标记下传。为了节省时间,我们不能每次都递归到叶子节点计算,所以我们为每一个区间打上一个标记,代表这个区间所有数都加上了这个数。当我们需要遍历到一个节点,准备访问其叶子节点的时候,我们就把标记下放。因为标记下放的时间通常可以不计,默认为
O
(
1
)
O(1)
O(1),所以我们就在遍历节点的时候随便把标记下放了,没有增加时间复杂度又保证了正确性。
总的时间复杂度: O ( ( M + N ) × l o g N ) O((M+N) \times log\ N) O((M+N)×log N),可以通过本题。
【 代 码 】 : \color{blue}{【代码】:} 【代码】:
const int N=1e5+100;
#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;
}//快读程序
ll sum[N<<2],add[N<<2];
inline void pushup(int o){
sum[o]=sum[o<<1]+sum[o<<1|1];
}//计算当前节点所代表的区间和
inline void pushdown(int o,int l,int r){
ll tag=add[o];add[o]=0ll;
add[o<<1]+=tag;add[o<<1|1]+=tag;
register int mid=(l+r)>>1;
sum[o<<1]+=tag*(mid-l+1);
sum[o<<1|1]+=tag*(r-mid);
}//标记下放,时间复杂度可认为是O(1)
void build(int o,int l,int r){
sum[o]=add[o]=0ll;
if (l==r) return;
register int mid=(l+r)>>1;
build(o<<1,l,mid);
build(o<<1|1,mid+1,r);
pushup(o);return;
}//基于本题的建树操作
void updata(int o,int l,int r,int p,int q,ll v){
if (l>q||r<p) return;
if (p<=l&&r<=q){
sum[o]+=v*(r-l+1);
add[o]+=v;return;
}
if (add[o]) pushdown(o,l,r);
register int mid=(l+r)>>1;
updata(o<<1,l,mid,p,q,v);
updata(o<<1|1,mid+1,r,p,q,v);
pushup(o);return;
}//区间修改:把区间[p,q]的每一个数都加上v
ll query(int o,int l,int r,int p,int q){
if (l>q||r<p) return 0ll;
if (p<=l&&r<=q) return sum[o];
if (add[o]) pushdown(o,l,r);
register int mid=(l+r)>>1;
register ll ans=0ll;
ans+=query(o<<1,l,mid,p,q);
ans+=query(o<<1|1,mid+1,r,p,q);
return ans;
}//询问操作:询问区间[p,q]的总和
int n,m;ll a[N];
int main(){
n=read();m=read();
for(int i=1;i<=n;i++)
a[i]=read();
build(1,1,n);
for(int i=1;i<=m;i++){
ll opt=read(),l=read();
if (opt==1){
ll r=read(),f=read(),d=read();
if (l==r){
updata(1,1,n,l,l,f);
if (l!=n) updata(1,1,n,l+1,l+1,-f);
}
else{
updata(1,1,n,l,l,f);updata(1,1,n,l+1,r,d);
if (r!=n) updata(1,1,n,r+1,r+1,-f-(r-l)*d);
}
}
else printf("%lld\n",query(1,1,n,1,l)+a[l]);
}
return 0;
}