动态区间子段和(KTT)
顾名思义,动态区间子段和是维护区间子段和的算法,其复杂度一般是 \(n\log^3 n\) 级别的,但是基本上卡不满,基本上可以认为是 \(n \log^2 n\) 的。(毕竟 EI 本人都不太卡的满)
P5693 EI 的第六分块
模板题。
操作是区间加和求区间最大子段和。这两个操作单独分开做的话就是简单的。区间加不必多说,区间最大子段和就是维护一个前缀最大、后缀最大和总体最大直接合并即可。现在看看两个东西合起来怎么做。
我们将正常最大子段和的式子写一写看能不能找一些性质。
其中 \(lm\) 为前缀最大子段和,\(rm\) 为后缀最大子段和,\(tm\) 为整体的区间最大子段和。还是比较直观的。
这个时候我们假设对于 \(u\) 这个节点表示的区间全部加上一个比较小的值 \(w\),使得对于 \(u\) 子树中以及 \(u\) 本身的所有 \(lm,rm,tm\) 的取 \(\max\) 的位置没有改变。什么意思呢?就是比如说原来 \(lm_{ls}\) 比 \(sum_{ls}+rm_{ls}\) 大,于是 \(lm_u\) 就取的 \(lm_{ls}\)。现在即使整个 \(u\) 的区间都加了一个较小的 \(w\),但是这个时候 \(lm_u\) 的值仍然比 \(sum_{ls}+rm_{ls}\) 大,\(lm_u\) 仍然是取的 \(lm_{ls}\)。
如果这样的情况出现,那么对于 \(u\) 而言这三个变量的维护就是方便的了。只需要知道 \(lm_{ls}\) 等最大前缀的长度,就可以 \(O(1)\) 处理出 \(u\) 对应的三个变量的大小。
那如果改变了呢?EI 的分析告诉我们,我们只需要向左右儿子递归,直到有这么一些点满足上面的条件即可。这样看着非常的暴力,但是其均摊下来复杂度的上界是 \((n+q)\log^3 n\) 的,其中 \(q\) 为询问的次数。同时很难构造出数据卡满这个上界,一般都可以视为 \((n+q)\log n\) 的复杂度。
那我们如何知道到底这个加的 \(w\) 是否足够的小呢?我们考虑对于每一个点都维护一个阈值 \(jd\)。如果 \(w>jd\),就代表这个值过大了,想做有儿子递归,否则就如上直接 \(O(1)\) 修改 即可。
考虑这个 \(jd\) 如何维护。显然有 \(jd\) 先要向左右儿子的 \(jd\) 取 \(\min\),因为左右儿子的变了自身的值也需要重新计算。然后考虑自己的这三个变量何时可能出现取 \(\max\) 的位置有改变。
我们类比区间加,维护一个加法标记 \(tag_u\),于是对于一个区间的子串,其真实值就是
发现这个东西显然就是一个一次函数的形式。于是我们对于上面式子里的每个 \(\max\) 里的值,都去记录一下其区间的长度与加之前的值,算一下两两之间交点的最小值,这个东西就是上文所说的 \(jd\)。
然后修改和查询就是正常做,复杂度 \(O(n+q)\log^3 n\),看起来极限实际上随便过。
code
细节较多。
实际上实现的时候是开了一个结构体来维护的,看起来和普通的线段树的形式不太一样,需要注意一些细节,学习一些相对优秀的写法,比如说求两个直线的交点之类的。当然还需要注意这个交点的大小是向下取整的,因为 \(w>jd\) 的时候才向左右子树递归。
然后标记下方之后需要更新一次函数的截距也就是所谓“加之前的值”,因为标记下方之后就变成加之后的值了。在本质上就是 \(y\) 轴向右平移了一段距离,所以 \(jd\) 也要修改。
然后变量名就直接将子串长度称为 \(k\),加之前的值设为 \(b\)。
多看代码理解一下即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ld double
const int N=4e5+7,inf=2e18+7;
int n,a[N],Q;
namespace KTT{
#define ls (u<<1)
#define rs (u<<1|1)
#define mid ((l+r)>>1)
struct line{int k,b;};
line operator + (line a,line b){return (line){a.k+b.k,a.b+b.b};}
line operator * (line a,int b){return (line){a.k,a.k*b+a.b};}
struct node{line l,r,t,sum;int jd;}tr[N<<2];
int tag[N<<2];
line cmp(line a,line b,int &x){
if(a.k<b.k||(a.k==b.k&&a.b<b.b))swap(a,b);
if(a.b>=b.b)return a;
x=min(x,(a.b-b.b)/(b.k-a.k));
return b;
}
node operator + (node x,node y){
node z;z.jd=min(x.jd,y.jd);
z.t=cmp(cmp(x.t,y.t,z.jd),x.r+y.l,z.jd);
z.l=cmp(x.l,x.sum+y.l,z.jd);
z.r=cmp(y.r,y.sum+x.r,z.jd);
z.sum=x.sum+y.sum;
return z;
}
void push_up(int u){tr[u]=tr[ls]+tr[rs];}
void update(int u,int w){
if(w>tr[u].jd)update(ls,tag[u]+w),update(rs,tag[u]+w),tag[u]=0,push_up(u);
else tag[u]+=w,tr[u].jd-=w,tr[u].l=tr[u].l*w,tr[u].r=tr[u].r*w,tr[u].sum=tr[u].sum*w,tr[u].t=tr[u].t*w;
}
void push_down(int u){if(tag[u])update(ls,tag[u]),update(rs,tag[u]),tag[u]=0;}
void build(int u,int l,int r){
if(l==r){line tmp={1,a[l]};tr[u]={tmp,tmp,tmp,tmp,inf};return;}
build(ls,l,mid),build(rs,mid+1,r);push_up(u);
}
void modify(int u,int l,int r,int ql,int qr,int w){
if(ql<=l&&r<=qr){update(u,w);return;}
push_down(u);if(ql<=mid)modify(ls,l,mid,ql,qr,w);if(qr>mid)modify(rs,mid+1,r,ql,qr,w);
push_up(u);
}
node query(int u,int l,int r,int ql,int qr){
if(ql<=l&&r<=qr)return tr[u];
push_down(u);
if(qr<=mid)return query(ls,l,mid,ql,qr);if(ql>mid)return query(rs,mid+1,r,ql,qr);
return query(ls,l,mid,ql,qr)+query(rs,mid+1,r,ql,qr);
}
}
using namespace KTT;
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>Q;for(int i=1;i<=n;i++)cin>>a[i];
build(1,1,n);
while(Q--){
int op,l,r,x;cin>>op>>l>>r;
if(op==1)cin>>x,modify(1,1,n,l,r,x);
else cout<<max(0ll,query(1,1,n,l,r).t.b)<<'\n';
}
return 0;
}
P6792 [SNOI2020] 区间和
吉司机线段树套 KTT。
还是发现两个操作分开都会做,于是两个操作合在一起就考虑把两个数据结构拼在一起。
发现对于吉司机线段树而言,线段树的节点上其可能修改的值有且仅有最小值,因此我们上文的斜率也就是“这个子串的长度”变成了字串内最小值的个数,所谓的区间加标记也变成了单独针对最大值的标记。
然后就发现其余操作是类似的,直接将两者的操作结合一下即可。唯一需要注意的点是在 push_up 的时候,由于我们要维护最大值和次大值,因此不是最大值的那一边的所有信息的斜率都要变成 0,因为我们上面改了一下定义,不是最大值的就对斜率没有贡献也就是最大值的长度在这一边为 0。
听人说如果不是区间加的话复杂度是 \((n+q)\log^2 n\) 的,不知道。
code
自己实现的时候写的很快,然后就调了一下午。注意到 update 函数中我一开始在第一行的判断中没有加入 ||w<=mf[u] 而只有前半段。这样就会导致即使 \(tag_u\) 是一开始的初始值 \(-inf\) 但是不满足后半段的条件,其也会进入函数,然后就调了一下午。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+7,inf=1e18+7;
int n,Q,a[N];
namespace KTT{
#define ls (u<<1)
#define rs (u<<1|1)
#define mid ((l+r)>>1)
struct line{int k,b;};
line operator + (line a,line b){return (line){a.k+b.k,a.b+b.b};}
line operator * (line a,int b){return (line){a.k,a.k*b+a.b};}
struct node{line l,r,t,sum;int jd;}tr[N<<2];
node init(int u){node tmp=tr[u];tmp.l.k=tmp.r.k=tmp.t.k=tmp.sum.k=0,tmp.jd=inf;return tmp;}
int tag[N<<2],mf[N<<2],ms[N<<2];
line cmp(line a,line b,int &x){
if(a.k<b.k||(a.k==b.k&&a.b<b.b))swap(a,b);
if(a.b>=b.b)return a;
x=min(x,(a.b-b.b)/(b.k-a.k));
return b;
}
node operator + (node x,node y){
node z;z.jd=min(x.jd,y.jd);
z.t=cmp(cmp(x.t,y.t,z.jd),x.r+y.l,z.jd);
z.l=cmp(x.l,x.sum+y.l,z.jd);
z.r=cmp(y.r,y.sum+x.r,z.jd);
z.sum=x.sum+y.sum;
return z;
}
void push_up(int u){
mf[u]=min(mf[ls],mf[rs]);
if(mf[ls]==mf[rs])ms[u]=min(ms[ls],ms[rs]),tr[u]=tr[ls]+tr[rs];
if(mf[ls]<mf[rs])ms[u]=min(ms[ls],mf[rs]),tr[u]=tr[ls]+init(rs);
if(mf[ls]>mf[rs])ms[u]=min(mf[ls],ms[rs]),tr[u]=init(ls)+tr[rs];
}
void update(int u,int w){
if(w<=tag[u]||w<=mf[u])return;tag[u]=w,w=w-mf[u],mf[u]+=w;
if(w>tr[u].jd)update(ls,tag[u]),update(rs,tag[u]),tag[u]=-inf,push_up(u);
else tr[u].jd-=w,tr[u].l=tr[u].l*w,tr[u].r=tr[u].r*w,tr[u].sum=tr[u].sum*w,tr[u].t=tr[u].t*w;
}
void push_down(int u){if(tag[u]!=-inf)update(ls,tag[u]),update(rs,tag[u]);tag[u]=-inf;}
void build(int u,int l,int r){
tag[u]=-inf;
if(l==r){line tmp={1,a[l]};tr[u]={tmp,tmp,tmp,tmp,inf},mf[u]=a[l],ms[u]=inf;return;}
build(ls,l,mid),build(rs,mid+1,r);push_up(u);
}
void modify(int u,int l,int r,int ql,int qr,int w){
if(w<=mf[u])return;
if(ql<=l&&r<=qr&&w<ms[u]){update(u,w);return;}
push_down(u);if(ql<=mid)modify(ls,l,mid,ql,qr,w);if(qr>mid)modify(rs,mid+1,r,ql,qr,w);
push_up(u);
}
node query(int u,int l,int r,int ql,int qr){
if(ql<=l&&r<=qr)return tr[u];
push_down(u);
if(qr<=mid)return query(ls,l,mid,ql,qr);if(ql>mid)return query(rs,mid+1,r,ql,qr);
return query(ls,l,mid,ql,qr)+query(rs,mid+1,r,ql,qr);
}
}
using namespace KTT;
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>Q;for(int i=1;i<=n;i++)cin>>a[i];
build(1,1,n);
while(Q--){
int op,l,r,x;cin>>op>>l>>r;
if(op==0)cin>>x,modify(1,1,n,l,r,x);
else cout<<max(0ll,query(1,1,n,l,r).t.b)<<'\n';
}
return 0;
}

浙公网安备 33010602011771号