对数据结构的爱
为了信仰来做这个题。
然后自己被题解和自己绕晕了好久……所以我会尽量讲得清楚一点。
由于 1e6 的数据范围,所以猜测是一个 polylog 的做法,来考虑线段树。
然后考虑需要在线段树节点上维护什么:
每一次的模拟都需要一个数与当前区间进行运算——所以考虑以某种方式在线段树上维护一个分段函数。
如果这样,我们从左到右依次将得到的上一个函数值代入这个区间的函数,就可以在
接下来就考虑如何对每一个线段树来保存,计算这个函数。
首先考虑存储。我们发现,这一个函数的图像,就是阶梯状地不断减 p,同时减 p 的断点个数不超过区间长度。
所以我们大可对于每一个区间维护可能出现的每一个断点——对于区间
如果我们对每一个区间维护出来了这个数组,我们的查询就已经可以在单次
然后就可以开始考虑怎么求这个数组了。直接求是不现实的,根据线段树上维护信息的一般定式,我们考虑如何来 pushup。
对于一组分属左右区间的
直接合并是平方复杂度,考虑优化。没什么其他路可走,就只能考虑有没有单调性来加快计算的速度。
先说明一个引理:对于任意一个区间,
这个的证明是显然的:将
而我们如果要使得函数值在这个区间上多减一次 p,必然要在这个 0 的基础上再多加 p 以抵消后面的那个非正数才行。
我们令
定理:
证明:因为
有了这个简单的定理,我们只要在能动 y 的时候尽量动 y,那就可以保证答案正确。(因为每一次都用的是最优的去更新能更新的每一个)。复杂度线性。
综上,所有的预处理可以在
代码:1.3K,不折不扣的小清新数据结构题。
#include <cstdio>
#include <vector>
#include <algorithm>
using std::min;using std::max;
typedef long long LL;
const LL inf = 1e18;
const int maxn = 1e6+5;
int n,m,p,a[maxn];
LL sum[maxn<<2];
std :: vector <LL> c[maxn<<2];
void pushup(int k,int l,int r,int mid){
sum[k] = sum[k<<1] + sum[k<<1|1];
for(int x=0,y=0;x<=mid-l+1;++x){
if(y==r-mid+1)--y;
for(;y<=r-mid;++y){
LL nd = c[k<<1][x+1]-1-1ll*x*p+sum[k<<1];
if(c[k<<1|1][y] > nd){--y;break;}
c[k][x+y] = min(c[k][x+y],max(c[k<<1][x],c[k<<1|1][y]+1ll*x*p-sum[k<<1]));
}
}
}
void build(int k,int l,int r){
c[k].resize(r-l+3);for(int i=0;i<=r-l+2;++i)c[k][i] = (i?inf:-inf);
if(l == r)return sum[k]=a[l],c[k][1]=p-a[l],void();
int mid = l+r>>1;
build(k<<1,l,mid),build(k<<1|1,mid+1,r),pushup(k,l,r,mid);
}
LL getans(int k,int l,int r,int x,int y,LL in){
if(l == x && r == y){
int pos = std :: upper_bound(c[k].begin(),c[k].end(),in)-c[k].begin()-1;
return in+sum[k]-1ll*p*pos;
}
int mid = l+r>>1;
if(mid < x)return getans(k<<1|1,mid+1,r,x,y,in);
if(y <= mid)return getans(k<<1,l,mid,x,y,in);
return getans(k<<1|1,mid+1,r,mid+1,y,getans(k<<1,l,mid,x,mid,in));
}
int main(){
scanf("%d %d %d",&n,&m,&p);
for(int i=1;i<=n;++i)scanf("%d",&a[i]);
build(1,1,n);while(m--){int l,r;scanf("%d %d",&l,&r),printf("%lld\n",getans(1,1,n,l,r,0));}
return 0;
}