题解:P12762 [POI 2018 R2] 电信中继站 Transceivers
二次差分+树状数组
前言
场上被创飞了,见过区间加等差序列,没见过要求区间查的,还是太菜了,本题我目前知道两个做法,一种是在线段树上打首项与公差的标记的(题解区有),一种是本题解待会要讲的,比较神秘,我也是从学长那里学来的(学得不是很精髓,可能讲得不太好,如有狗叫之处欢迎指出)。
同时个人认为这题能到上位绿下位蓝的程度,建议蓝一下(小声)。
正文
先来看点做法没什么关联但是是本题弱化版的 P1438 无聊的数列
两个操作分别是对一个区间加上首项为
由于区间加的值有等差的性质,我们可以考虑把维护的原序列转换成差分序列,在差分序列上进行单点加首项,区间加公差即可完成一操作。单点查询
再回到这道题,题目诡异的查询操作竟然是对原序列求区间和,如果我们继续按照
设原序列为:
差分一次后:
我们暂且把
困难在于,式子的后半部分是一个区间信息,前面又带一个
说起来太抽象了,我直接说做法,你们进行一些体会。
我们把差分序列再差分一次得到:
这时对于修改操作就变成了两个单点修改,在
考虑把每一个
后半部分可以直接化出式子,变成:
简单说一下后半部分怎么化的(反正我一开始没反应过来):考虑
我们把式子拆拆再归归类:
发现有关
这样一来我的查询就优化成了正常的
代码:
#include<bits/stdc++.h>
#define ll long long
#define int long long
#define lb(x) ((x)&(-x))
using namespace std;
const int N=3e5+5;
int n,m;
struct {
int sx,gc;
}ev[N];
struct BIT{
int t[N];
void add(int x,int v){
while(x<=n){
t[x]+=v;
x+=lb(x);
}
}
int query(int x){
int res=0;
while(x){
res+=t[x];
x-=lb(x);
}
return res;
}
}p0,p1,p2;
void add(int x,int v){
p0.add(x,v),p1.add(x,v*x),p2.add(x,v*x*x);
}
void update(int l,int r,int k,int d){
//点修首项
add(l,k),add(l+1,d-k);
//点修右端点,二次差分后差值为等差数列的和
add(r+1,-(k+d*(r-l+1))),add(r+2,k+d*(r-l));
}
int query(int x){
int res=0;
//0次项计算,后面也可写成(x*x+3*x+2)
res+=p0.query(x)*(x+1)*(x+2);
//一次项
res-=(2*x+3)*p1.query(x);
//二次项
res+=p2.query(x);
return res/2;
}
void xpigeon(){
cin>>n>>m;
for(int i=1;i<=m;i++){
char opt;
cin>>opt;
if(opt=='P'){
int x,s,a;
cin>>x>>s>>a;
ev[x].sx=s;
ev[x].gc=a;
int l=max(1ll,x-s/a),r=min(x+s/a,n);
update(l,x-1,s-a*(x-l),a);
update(x,r,s,-a);
}else if(opt=='U'){
int x,s,a;
cin>>x;
s=-ev[x].sx,a=-ev[x].gc;
int l=max(1ll,x-s/a),r=min(x+s/a,n);
update(l,x-1,s-a*(x-l),a);
update(x,r,s,-a);
}else{
int l,r;
cin>>l>>r;
cout<<(query(r)-query(l-1))/(r-l+1)<<'\n';
}
}
return ;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
xpigeon();
return 0;
}