【Ynoi2013】对数据结构的爱/【Codeforces 1172F】Nauuo and Bug
线段树维护分段函数
题目大意:
给你如下伪代码:
给定 $p$ 和数列 $a$,有 $m$ 组询问,每次给定 $l,r$,问 sum(a,l,r,p)
的值是多少。
题解:
考虑当前的数为 $v$,经过连续一段数后,变成了 $f(v)$。这个 $f(v)$ 是一个分段函数,每段是一个一次函数,且一次项系数为 $1$。
对 $n$ 个数的分段函数,分成的段数为 $n+1$。考虑当 $n=1$ 的如下情况:
考虑要新加上一个数并执行modadd
操作。那么,分段函数所有在 $p$ 及以上的部分,都会整体下移 $p$。
想象一下,前 $n$ 段,每段超过 $p$ 的部分,下移后一定会和下一段恰好连接起来,形成新的一段。然后最后那段值域到 $+\infty$ 的部分,下移后会形成新的一段,所以会新增一段。
考虑用线段树维护每个区间的数的分段函数。查询的时候,在线段树结点的分段函数上二分所在段,得到当前的数经过这个区间后会变成的数字即可。单次查询的复杂度为 $O(\log^2 n)$。
考虑构造这棵线段树,用二分的方法把左边和右边复合的时间复杂度是 $O(n\log^2 n)$ 的,无法通过。
我们发现,分段函数每段的长度都至少为 $p$。仔细思考分段函数上下移动的过程,如果遇到的一直是加操作,那么一段被截到下一部分,必定有一段等长的从上一段补过来,所以中间的那些段都有相同的高度 $h$。如果遇到减操作,那么最后一段的高度会高于 $h$,而中间段,最高高度仍不变。由于斜率为 $1$,每段纵坐标差至少为 $p$,故横坐标覆盖的范围也是 $p$。
那么我们复合两段函数的时候,按横坐标从小到大的顺序枚举左边的 $f$,并用一个指针指向当前 $f$ 函数值对应的右边 $g$ 的段。然后,$f$ 的下一段和当前段的函数值相差为 $p$,所以 $g$ 的指针最多只需往前移动 $1$。所以总的复杂度是线性的。
那么维护区间的分段函数的复杂度降为 $O(n\log n)$。
总时间复杂度 $O(n\log n+m\log^2 n)$。
由于线段树每层需要记录 $O(n)$ 个信息,所以空间复杂度 $O(n\log n)$。
Code:
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const LL inf=5e16;
const int N=1.1e6;
struct func{
LL r,b;
inline LL operator()(LL x){return x+b;}
inline bool operator<(const func&rhs)const{return r<rhs.r;}
};
func*d[N<<1];
int n,m,md,sz[N<<1];
void merge(func*&h,func*F,func*g,int&szh,const int&szf,const int&szg){
static func sta[N<<1],f[N<<1];int top=0;sta[0].b=inf;
for(int i=0;i<szf;++i)f[i+1]=F[i];f[0].r=-inf;
for(int i=1,it=0;i<=szf;){
const LL L=max(-inf,f[i](f[i-1].r+1)),R=min(inf,f[i](f[i].r));
while(it<szg&&L>g[it].r)++it;
if(R<=g[it].r){
LL b=f[i].b+g[it].b;
if(sta[top].b!=b)
sta[++top]=(func){f[i].r,b};
else
sta[top].r=f[i].r;
++i;
}else{
const LL mid=g[it].r-f[i].b;
LL b=f[i].b+g[it].b;
if(sta[top].b!=b)
sta[++top]=(func){mid,f[i].b+g[it].b};
else
sta[top].r=mid;
f[i-1].r=mid;
}
it-=!!it;
}
h=new func[szh=top];
for(int i=1;i<=top;++i)
h[i-1]=sta[i];
}
void build(int l,int r,int o){
if(l==r){
int x;
cin>>x;
d[o]=new func[sz[o]=2];
d[o][0]=(func){md-x-1,x},d[o][1]=(func){inf,x-md};
}else{
const int mid=l+r>>1;
build(l,mid,o<<1),build(mid+1,r,o<<1|1);
merge(d[o],d[o<<1],d[o<<1|1],sz[o],sz[o<<1],sz[o<<1|1]);
}
}
void trans(int l,int r,int o,const int&L,const int&R,LL&ans){
if(L<=l&&r<=R){
ans=(*(lower_bound(d[o],d[o]+sz[o],(func){ans})))(ans);
}else{
const int mid=l+r>>1;
if(L<=mid)trans(l,mid,o<<1,L,R,ans);
if(mid<R)trans(mid+1,r,o<<1|1,L,R,ans);
}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>m>>md;
build(1,n,1);
while(m--){
LL ans=0;
int l,r;
cin>>l>>r;
trans(1,n,1,l,r,ans);
cout<<ans<<'\n';
}
return 0;
}