洛谷P1438 无聊的数列 [zkw线段树]

时间:2021-08-21 13:56:53
 题目传送门

无聊的数列

题目背景

无聊的YYB总喜欢搞出一些正常人无法搞出的东西。有一天,无聊的YYB想出了一道无聊的题:无聊的数列。。。(K峰:这题不是傻X题吗)

题目描述

维护一个数列{a[i]},支持两种操作:

1、1 L R K D:给出一个长度等于R-L+1的等差数列,首项为K,公差为D,并将它对应加到a[L]~a[R]的每一个数上。即:令a[L]=a[L]+K,a[L+1]=a[L+1]+K+D,

a[L+2]=a[L+2]+K+2D……a[R]=a[R]+K+(R-L)D。

2、2 P:询问序列的第P个数的值a[P]。

输入输出格式

输入格式:

第一行两个整数数n,m,表示数列长度和操作个数。

第二行n个整数,第i个数表示a[i](i=1,2,3…,n)。

接下来的m行,表示m个操作,有两种形式:

1 L R K D

2 P 字母意义见描述(L≤R)。

输出格式:

对于每个询问,输出答案,每个答案占一行。

输入输出样例

输入样例#1:
5 2
1 2 3 4 5
1 2 4 1 2
2 3
输出样例#1:
6

说明

数据规模:

0≤n,m≤100000

|a[i]|,|K|,|D|≤200

Hint:

有没有巧妙的做法?


  分析:

  刚学完$zkw$线段树找道题来练练手。

  关于这题,如果直接上一般的区间修改+单点查询的话很难做,但是如果我们换个思路,运用差分的思想就会变得很容易了。

  我们把线段树维护的内容变成原数列的差分数列,也就是说$seg[i]=a[i]-a[i-1]$,那么单点查询的操作就变成了区间查询。但是修改操作呢?

  不难想到,因为是等差数列,所以差分数组的修改值实际上就是等差数列的公差。所以修改区间$[l,r]$时,先单点修改$l$,修改值为等差数列首项,再单点修改$r+1$,修改值为$-(K+(r-l)*D)$(自己想想为什么),最后再修改区间$[l+1,r]$,修改值为公差。就这样了。

  写的$zkw$线段树感觉还是常数有点大,总共跑了$335ms$,别人跑的快的只用$150ms$,不过至少比普通线段树快多了。

  Code:

//It is made by HolseLee on 5th Sep 2018
//Luogu.org P1438
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<iomanip>
#include<algorithm>
using namespace std; typedef long long ll;
const int N=1e5+;
int n,m,maxn;
ll a[N],seg[N<<],sign[N<<]; template<typename re>
inline void read(re &x)
{
x=; char ch=getchar(); bool flag=false;
while( ch<'' || ch>'' ) {
if( ch=='-' ) flag=true;
ch=getchar();
}
while( ch>='' && ch<='' ) {
x=(x<<)+(x<<)+ch-'';
ch=getchar();
}
flag ? x*=(-) : ;
} inline void modify(int pos,ll x)
{
for(pos+=maxn; pos; pos>>=) seg[pos]+=x;
} inline void update(int l,int r,ll x)
{
ll lum=,rum=,num=;
for(l=l+maxn-,r=r+maxn+; l^r^; l>>=,r>>=,num<<=) {
seg[l]+=x*lum;
seg[r]+=x*rum;
if( ~l& ) sign[l^]+=x, seg[l^]+=x*num, lum+=num;
if( r& ) sign[r^]+=x, seg[r^]+=x*num, rum+=num;
}
for(; l; l>>=,r>>=) {
seg[l]+=x*lum;
seg[r]+=x*rum;
}
} inline ll quary(int l,int r)
{
ll ret=,lum=,rum=,num=;
for(l=l+maxn-,r=r+maxn+; l^r^; l>>=,r>>=,num<<=) {
if( sign[l] ) ret+=sign[l]*lum;
if( sign[r] ) ret+=sign[r]*rum;
if( ~l& ) ret+=seg[l^], lum+=num;
if( r& ) ret+=seg[r^], rum+=num;
}
for(; l; l>>=,r>>=)
ret+=sign[l]*lum, ret+=sign[r]*rum;
return ret;
} int main()
{
read(n); read(m); maxn=;
for(; maxn<=n+; maxn<<=);
for(int i=; i<=n; ++i) {
read(a[i]); seg[maxn+i]=a[i]-a[i-];
}
for(int i=maxn-; i>=; --i) seg[i]=seg[i<<]+seg[i<<|];
int opt,l,r; ll x,y;
for(int i=; i<=m; ++i) {
read(opt);
if( opt== ) {
read(l), read(r), read(x), read(y);
modify(l,x); modify(r+,-(x+(ll)(r-l)*y));
update(l+,r,y);
} else {
read(x);
printf("%lld\n",quary(,x));
}
}
return ;
}