KTT
KTT
KTT 是线段树的一种变种,用来维护若干个形如 \(y=ka+b\) 的一次函数的相关信息。每次修改包括给 \(k\) 修改和给 \(b\) 修改,查询包括区间求和、区间求最大值等。
线段树上的每个区间其实只需要维护 \(a,b\) 两个值即可,那么每一次对 \(k\) 的修改就可以直接在 \(b\) 上修改,即 \(b\xleftarrow+b+ka\)。那么每个位置的具体的值就是 \(b\),查询时输出 \(b\) 的值即可。
然后就到了 KTT 的关键:在每个节点维护一个被称为 \(\mathit{intr}\) 的变量,表示当前区间的交替阈值,具体来说就是当前区间的 \(k\) 增大到 \(\mathit{intr}\) 这个值时,区间最大值会发生改变。从几何意义上理解,可以看作是两个一次函数相交,\(\mathit{intr}\) 就是交点的横坐标。
考虑如何更新 \(\mathit{intr}\) 这个值,其实是容易的,即对左右儿子的 \(\mathit{intr}\) 和左右儿子维护的最大函数取交点横坐标,三者取 \(\min\) 即可。
\(\mathit{intr}\) 这个变量的作用其实在于告诉我们什么时候我们维护的最大值会改变,也就是函数发生交替。当我们对 \(k\) 的值进行修改时,比如对 \(k\) 加 \(v\),那么就给当前区间的 \(\mathit{intr}\) 减去 \(v\)。当任意时刻 \(\mathit{intr}\le0\) 则说明我们该更新最大值了,此时将所有的标记下放,可以看作是暴力重构子树。
需要注意的是,KTT 维护的问题需要保证 \(k\) 的增量均为正或均为负。时间复杂度经过复杂的势能分析是上界 \(O(n\log^3n)\) 的,但由于跑不满其实可以看作 \(O(n\log^2n)\)。
例 1 P5693 EI 的第六分块
其实这道题才算 KTT 的真正板题。本题我们要维护区间加和区间最大子段和。一般对于最大子段和的做法就是维护 \(\mathit{ls},\mathit{rs},\mathit{ms},\mathit{sm}\) 等变量通过 pushup 来维护信息。那么本题我们把这些变量都变成函数形式来进行维护,\(y=ka+b\) 中的 \(a\) 是区间维护的长度,那么一次区间加可以看作 \(b\xleftarrow+b+ka\),就可以用 KTT 来维护了。\(\mathit{intr}\) 要考虑的情况比较复杂,建议封装来写。
其实这玩意还是结合代码理解比较靠谱。
#include<bits/stdc++.h>
#define fw fwrite(obuf,p3-obuf,1,stdout)
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++)
#define putchar(x) (p3-obuf<1<<20?(*p3++=(x)):(fw,p3=obuf,*p3++=(x)))
#define il inline
using namespace std;
char buf[1<<20],obuf[1<<20],*p1=buf,*p2=buf,*p3=obuf,str[20<<2];
il int read(){
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch))ch=='-'&&(f=-1),ch=getchar();
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*f;
}
template<typename T>
il void write(T x,char sf='\n'){
if(x<0)putchar('-'),x=~x+1;
int top=0;
do str[top++]=x%10,x/=10;while(x);
while(top)putchar(str[--top]+48);
if(sf^'#')putchar(sf);
}
using ll=long long;
constexpr int MAXN=4e5+5;
constexpr ll INF=0x3f3f3f3f3f3f3f3fll;
int n,q,a[MAXN];
struct{
#define lp p<<1
#define rp p<<1|1
struct Line{
ll a,b;
il Line(ll a=0,ll b=0):a(a),b(b){}
friend il Line operator+(Line a,Line b){
return {a.a+b.a,a.b+b.b};
}
};
static il pair<Line,ll> max(Line a,Line b){
if(a.a<b.a||(a.a==b.a&&a.b<b.b)) swap(a,b);
if(a.b>=b.b) return {a,INF};
else return {b,(b.b-a.b)/(a.a-b.a)};
}
struct Node{
Line ls,rs,ms,sm;
ll ir;
il Node(Line a=Line(),ll b=0){
ls=rs=ms=sm=a;
ir=b;
}
friend il Node operator+(Node a,Node b){
Node res;
pair<Line,ll>tp;
res.ir=min(a.ir,b.ir);
tp=max(a.ls,a.sm+b.ls);
res.ls=tp.first;
res.ir=min(res.ir,tp.second);
tp=max(b.rs,b.sm+a.rs);
res.rs=tp.first;
res.ir=min(res.ir,tp.second);
tp=max(a.ms,b.ms);
res.ir=min(res.ir,tp.second);
tp=max(tp.first,a.rs+b.ls);
res.ms=tp.first;
res.ir=min(res.ir,tp.second);
res.sm=a.sm+b.sm;
return res;
}
};
struct KTT{
Node c;
ll lz;
}st[MAXN<<2];
il void psu(int p){
st[p].c=st[lp].c+st[rp].c;
}
il void pst(int p,ll k){
st[p].lz+=k;
st[p].c.ir-=k;
st[p].c.ls.b+=st[p].c.ls.a*k;
st[p].c.rs.b+=st[p].c.rs.a*k;
st[p].c.sm.b+=st[p].c.sm.a*k;
st[p].c.ms.b+=st[p].c.ms.a*k;
}
il void psd(int p){
pst(lp,st[p].lz);
pst(rp,st[p].lz);
st[p].lz=0;
}
il void build(int s,int t,int p){
if(s==t) return st[p].c=Node({1,a[s]},INF),void();
int mid=(s+t)>>1;
build(s,mid,lp),build(mid+1,t,rp);
psu(p);
}
il void redo(int p){
if(st[p].c.ir>=0) return;
psd(p);
redo(lp),redo(rp);
psu(p);
}
il void add(int l,int r,ll k,int s=1,int t=n,int p=1){
if(l<=s&&t<=r) return pst(p,k),redo(p);
int mid=(s+t)>>1;
psd(p);
if(l<=mid) add(l,r,k,s,mid,lp);
if(mid<r) add(l,r,k,mid+1,t,rp);
psu(p);
}
il Node query(int l,int r,int s=1,int t=n,int p=1){
if(l<=s&&t<=r) return st[p].c;
psd(p);
int mid=(s+t)>>1;
if(l>mid) return query(l,r,mid+1,t,rp);
if(mid>=r) return query(l,r,s,mid,lp);
return query(l,r,s,mid,lp)+query(l,r,mid+1,t,rp);
}
}T;
int main(){
n=read(),q=read();
for(int i=1;i<=n;i++) a[i]=read();
T.build(1,n,1);
while(q--){
int op=read(),l=read(),r=read();
if(op==1) T.add(l,r,read());
else write(max(0ll,T.query(l,r).ms.b));
}
return fw,0;
}
例 2 P5073 [Ynoi Easy Round 2015] 世上最幸福的女孩
这道题和上一道题是类似的,不同之处在于这题的增量不一定为正。但是这题的特殊之处在于修改是全局的,也就是说我们可以记录每一次查询之前的增量。把询问离线下来,按照增量排序,那么增量和增量之间的修改就一定是正的,我们只需特殊处理一下第一个询问,就能套上面 KTT 的板子了。
#include<bits/stdc++.h>
#define fw fwrite(obuf,p3-obuf,1,stdout)
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++)
#define putchar(x) (p3-obuf<1<<20?(*p3++=(x)):(fw,p3=obuf,*p3++=(x)))
#define il inline
using namespace std;
char buf[1<<20],obuf[1<<20],*p1=buf,*p2=buf,*p3=obuf,str[20<<2];
il int read(){
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch))ch=='-'&&(f=-1),ch=getchar();
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*f;
}
template<typename T>
il void write(T x,char sf='\n'){
if(x<0)putchar('-'),x=~x+1;
int top=0;
do str[top++]=x%10,x/=10;while(x);
while(top)putchar(str[--top]+48);
if(sf^'#')putchar(sf);
}
using ll=long long;
constexpr int MAXN=3e5+5,MAXM=6e5+5;
constexpr ll INF=0x3f3f3f3f3f3f3f3fll;
constexpr il ll max(ll a,ll b)noexcept{return a>b?a:b;}
constexpr il ll min(ll a,ll b)noexcept{return a<b?a:b;}
int n,m;
ll a[MAXN];
double inv[MAXN];
struct{
#define lp p<<1
#define rp p<<1|1
struct Line{
ll a,b;
il Line(ll a=0,ll b=0):a(a),b(b){}
friend il Line operator+(Line a,Line b){
return {a.a+b.a,a.b+b.b};
}
};
static il pair<Line,ll> max(Line a,Line b){
if(a.a<b.a||(a.a==b.a&&a.b<b.b)) swap(a,b);
if(a.b>=b.b) return {a,INF};
else return {b,(b.b-a.b)*inv[a.a-b.a]};
}
struct Node{
Line ls,rs,ms,sm;
ll ir;
il Node(Line a=Line(),ll b=0){
ls=rs=ms=sm=a;
ir=b;
}
friend il Node operator+(Node a,Node b){
Node res;
pair<Line,ll>tp;
res.ir=min(a.ir,b.ir);
tp=max(a.ls,a.sm+b.ls);
res.ls=tp.first;
res.ir=min(res.ir,tp.second);
tp=max(b.rs,b.sm+a.rs);
res.rs=tp.first;
res.ir=min(res.ir,tp.second);
tp=max(a.ms,b.ms);
res.ir=min(res.ir,tp.second);
tp=max(tp.first,a.rs+b.ls);
res.ms=tp.first;
res.ir=min(res.ir,tp.second);
res.sm=a.sm+b.sm;
return res;
}
};
struct KTT{
Node c;
ll lz;
}st[MAXN<<2];
il void psu(int p){
st[p].c=st[lp].c+st[rp].c;
}
il void pst(int p,ll k){
st[p].lz+=k;
st[p].c.ir-=k;
st[p].c.ls.b+=st[p].c.ls.a*k;
st[p].c.rs.b+=st[p].c.rs.a*k;
st[p].c.sm.b+=st[p].c.sm.a*k;
st[p].c.ms.b+=st[p].c.ms.a*k;
}
il void psd(int p){
pst(lp,st[p].lz);
pst(rp,st[p].lz);
st[p].lz=0;
}
il void build(int s,int t,int p){
if(s==t) return st[p].c=Node({1,a[s]},INF),void();
int mid=(s+t)>>1;
build(s,mid,lp),build(mid+1,t,rp);
psu(p);
}
il void redo(int p){
if(st[p].c.ir>=0) return;
psd(p);
redo(lp),redo(rp);
psu(p);
}
il void add(int l,int r,ll k,int s=1,int t=n,int p=1){
if(l<=s&&t<=r) return pst(p,k),redo(p);
int mid=(s+t)>>1;
psd(p);
if(l<=mid) add(l,r,k,s,mid,lp);
if(mid<r) add(l,r,k,mid+1,t,rp);
psu(p);
}
il Node query(int l,int r,int s=1,int t=n,int p=1){
if(l<=s&&t<=r) return st[p].c;
psd(p);
int mid=(s+t)>>1;
if(l>mid) return query(l,r,mid+1,t,rp);
if(mid>=r) return query(l,r,s,mid,lp);
return query(l,r,s,mid,lp)+query(l,r,mid+1,t,rp);
}
}T;
struct Node{
int id,l,r;
ll add;
il bool operator<(const Node&b)const{
return add<b.add;
}
}q[MAXM];
ll ans[MAXM];
int main(){
n=read(),m=read();
for(int i=1;i<=n;i++) a[i]=read(),inv[i]=1./i;
ll add=0;
int tot=0;
for(int i=1;i<=m;i++){
int op=read();
if(op==1){
int x=read();
add+=x;
}else{
int l=read(),r=read();
tot++;
q[tot]={tot,l,r,add};
}
}
sort(q+1,q+tot+1);
for(int i=1;i<=n;i++) a[i]+=q[1].add;
T.build(1,n,1);
ans[q[1].id]=max(T.query(q[1].l,q[1].r).ms.b,0ll);
for(int i=2;i<=tot;i++){
if(q[i].add!=q[i-1].add) T.add(1,n,q[i].add-q[i-1].add);
ans[q[i].id]=max(T.query(q[i].l,q[i].r).ms.b,0ll);
}
for(int i=1;i<=tot;i++) write(ans[i]);
return fw,0;
}
如果你被卡常了
-
fread快读; -
手写
max/min函数;constexpr il ll max(ll a,ll b)noexcept{return a>b?a:b;} constexpr il ll min(ll a,ll b)noexcept{return a<b?a:b;} -
预处理所有数的倒数,把除法改为乘倒数;
-
inline全部拉满; -
多交几次。

浙公网安备 33010602011771号