线段树进阶笔记

update:

  • 2024.10.23 整理了 Segment Tree Beat 与 历史最值操作

线段树是一个比较基础的数据结构,但其变形与拓展的难度依旧极高,仍有许多 人类の智慧 藏匿其中。

0. 线段树的双半群模型

一些群论的东西,但当维护信息 极多 时较为有用,具体可见 5. 历史最值问题
(写的很粗糙,详细请找群论内容)

形象的,线段树上每个节点都有 数据标记 两种信息,称作 DT

  • 则需要存在 DD=D 的转移,即 数据合并
  • 以及 DT=D,即 标记转移
  • 以及 TT=T,即 标记合并

同时还需要满足 结合律分配律,这是一个 半群,再存在一个 单位元 ϵ ,使 Tϵ=ϵT=T 则为 幺半群

则我们可以维护两个结构体,将三种转移加入即可。

这里举个例子。

区间加,区间乘,区间查询。则有 D={l,s}T={a,b} (乘法与加法标记)。
则有 (l1,s1)(l2,s2)=(l1+l2,s1+s2)
以及 (l,s)(a,b)=(l,as+lb)
以及 (a1,b1)(a2,b2)=(a1a2,b1a2+b2)

注意 顺序

当然,这些数据也可以用 矩阵 维护,如果你感兴趣(常数稍大)
区间历史操作,从矩阵乘法到标记 - EnofTaiPeople

参考文章

线段树维护分治信息略解 - 铃悬
数据结构闲谈:范围分治的「双半群」模型 - Meatherm

1. 可持久化线段树

1.1 算法介绍

首先可持久化线段树是 动态开点 的,本质上是通过记录 原来的信息 实现 时间域 上的查询,每次插入需要以上一个版本的节点为基础,新建节点进行更新,复杂度是 O(nlogV),一般是解决 区间 kth 的。

1.2 扩展应用

1.3 例题

I P2839 [国家集训队] middle

我们考虑二分 mid ,我们令 <mid 的值定为 1mid 定为 1,则我们即求区间 [a,b] 的后缀最大值, [b+1,c1] 的区间和即 [c,d] 的 前缀最大值,这个东西我们可以按照 权值大小从小到大 构造可持久化线段树,这样我们只需找到 mid 这颗树,求答案即可,若 ans0 则答案一定 大于等于 mid

代码

2. 李超线段树

李超树比较好些,凸包的细节太多。
貌似ktt可以代替,但我没学。

2.1 算法介绍

李超树是解决 直线插入,查询某一 横坐标 使纵坐标最大的直线。

对于值域在 [l,r] 中的直线,设 mid=l+r2,两条直线 p,q,若 f(p,mid)f(q,mid),则直线 p 比直线 q 大的值域一定至少包含改区间一半值域,所以我们在每个节点存该节点 x=mid 时纵坐标最大的直线编号,新加入一个直线,我们只需要判断是否 f(p,mid)f(q,mid),若是则交换,然后我们可以找出另一半不一定大的值域,递归进去即可,复杂度 logV

一般李超树都用的是标记永久化,所以我们查询的时候需要找到包含 x 点所有值域的最优答案。

对于插入一个 线段,我们可以先把线段的值域拆分,然后再加入线段即可,复杂度 O(nlog2V)

【模板】李超线段树

struct line{
	db k,b = -inf;
	int id; 
	line(db k,db b,int id):k(k),b(b),id(id){}
	line(){}
	db val(int x){return k * x + b;}
};
bool cmp(line a,line b,int x){return (fabs(a.val(x) - b.val(x)) < eps) ? a.id < b.id : a.val(x) > b.val(x);}

struct LiChao_tree{
    line mx[N<<5];
    line ask(line a,line b,int x){return cmp(a,b,x) ? a : b;}
    void modify(int p,int l,int r,line x){
        if(l > r)return;
        int mid = l + r >> 1;
        if(cmp(x,mx[p],mid))swap(mx[p],x);
        if(cmp(x,mx[p],l))modify(p<<1,l,mid,x);
        if(cmp(x,mx[p],r))modify(p<<1|1,mid+1,r,x);
    }
    void modify(int p,int l,int r,int L,int R,line x){
        if(L <= l && r <= R)return modify(p,l,r,x),void();
        int mid = l + r >> 1;
        if(L <= mid)modify(p<<1,l,mid,L,R,x);
        if(R > mid)modify(p<<1|1,mid+1,r,L,R,x);
    }
    line query(int p,int l,int r,int x){
        if(l == r)return mx[p];
        int mid = l + r >> 1;
        if(x <= mid)return ask(query(p<<1,l,mid,x),mx[p],x);
        else return ask(query(p<<1|1,mid+1,r,x),mx[p],x);
    }
}t; 



int main(){
    scanf("%d",&n);
    for(int i = 1;i <= n;i++){
        int op,a,b,c,d;
        scanf("%d%d",&op,&a);a = (a+las-1)%m1+1;
        if(op == 0)printf("%d\n",las = t.query(1,1,m1,a).id);
        else{
            scanf("%d%d%d",&b,&c,&d);
            b = (b+las-1)%m2+1,c = (c+las-1)%m1+1,d = (d+las-1)%m2+1;
            if(a > c)swap(a,c),swap(b,d);
            double k = 0,B = 0;
            if(a == c)B = max(b,d);
            else k = (double)(d - b) / (c - a),B = (double)b - a * k;
            t.modify(1,1,m1,a,c,{k,B,++m});
        }
    }

	return 0;

}

给出一个包装比较好的实现,不是很难写。

2.2 扩展应用

2.2.1 优化 DP(代替斜率优化)

斜率优化本质就是维护一些直线,通过建立凸包来寻找最大值。

李超树 可以直接维护直线,复杂度 O(nlogV)有时会劣于斜率优化。
但是有些性质会导致不可使用 李炒熟,注意甄别。

见例 I

2.2.2 动态开点

当横坐标值域过大时,可以用 动态开点,空间复杂度甚至是 O(n) 的。

感性证明一下:设当前直线 x 在值域 [l,r] 中,若当前值域没有直线,则不会向下递归,空间复杂度增加 O(1),否则该线段会向下递归知道找到没有直线的区间,复杂度也是增加 O(1)

2.2.3 李超线段树合并

与普通线段树合并类似。

y 合并到 x 节点上时,我们只需要将 y 节点上的直线插入到 x 上即可。

因为直线的总数是 O(n) 的,所以总复杂度依旧是 O(nlogV)

若我们用 动态开点 + 回收内存 则空间复杂度依旧是 O(n) 的。

2.2.4 可持久化李超树

动态开点,每加一条直线代表一个版本即可。

2.3 例题

I P8726 [蓝桥杯 2020 省 AB3] 旅行家

考虑 DP,则 fi=max(fj2+ai×ajbj),这个式子显然可以斜率优化,不过我们暴力一点,直接将其化为 fi=ajk×aix+fj2bjb,相当于有一些直线,给定横坐标,求最大纵坐标,这不就是我们李炒熟吗,复杂度 O(nlogV),而该题斜优复杂度是 O(n) 的,复杂度稍劣,常数稍大,不过 暴力,好想,好写

代码

II P9020 [USACO23JAN] Mana Collection P

首先我们考虑如何求每个点的贡献,可以发现只有最后一次经过某点的时间是有用的,我们可以考虑 最少失去的法力值,设其为 w ,则答案即为 s×mwn 较小,考虑状压 DP,因为询问规定了最终点,所以一维是不行的,设 fi,j 表示已经最后一次经过状态 i 中的点,且当前在 j 位置的最小答案,则有状态转移方程:

fi,j=minfla,k+dk,j×sla

其中 di,j 表示 ij 的最短路,si 表示状态 i 中所有节点的 m 和。

然后对于答案,即为 ans=sik×sx+(fi,jb),显然可以 李焯书 解决。

复杂度 O(2nn2+2nnlogV+qlogV),当然也可以维护凸包,但是瓶颈不在这,复杂度差不多。

代码

III COGS 3922. 删除题目

首先我们假设两条边 (u1,v1)(u2,v2) 中间未选择断边,则我们可以考虑 DP,设 fi 表示 必须选 (fai,i) 这条边时在其子树内走可以得到的最大贡献,则任意一点 v 在其子树内,则有转移方程 fi=maxvson(i)fv+sizev×(sizeusizev),这东西可以用 李炒熟 维护,对于树形结构可以用 李超树合并 解决子树问题。

但是这显然没完,上述所求的只是在一条链 从子树到祖先 上的路径,然后我们考虑在某个点合并两条链,假设合并两点 u,v,则需要再减去重复的贡献 sizeu×sizev,所以我们求得答案即为 fu+fvsizeu×sizev 的最大值,显然也可以用 李焯书,然后考虑如何合并,可以用 淀粉质,复杂度是 O(nlog2n) 的,但巨大长代码。

知周所众,dsu on tree 是可以代替一些淀粉质的,所以我们考虑用 dsu on tree,考虑每个点 x,保存其 重儿子 的李超树,其余节点暴力查询答案,然后进行 李焯书贺兵 即可,复杂度也是 O(nlog2n),但较好写,常数也小。

代码

IV CF1175G Yet Another Partiton Problem
神题

显然有 DP fi,k=minj=k1i1fj,k1+(ij)×maxl=j+1ial

这种东西一般决策单调性,但是没有。

对于内部的 max 我们显然可以单调栈处理,变为 max 值相同的几段,然后对于每一段设当前值为 w,则我们考虑求 fj,k1+(ij)×w=fj,k1j×w+i×w,先不考虑 i×w,然后显然可以建凸包斜率优化,求出每一段的最小值。

然后对于所有段,答案即为 fj,k1j×wb+i×wk 最小值,前面划线部分对于每一段是不变的,所以相当于是一些斜线求 min,可以 李炒熟 维护。

每加入一个新的决策,则单调栈中可能会合并一些区间,则我们建立的凸包需要合并,需要 启发式合并 保证复杂度为 O(nlogn),所以需要 deque 维护,而李超树需要撤销,可以可持久化解决。

总复杂度 O(nklogn)

傻逼凸包细节真多,要你李超树有何用?
难写的一批

代码

2.4 参考文章

线段树的高级用法 - Alex_wei

3. 线段树分治

更像是一种利用线段树结构的思想。

3.1 算法思想

主要思想就是 离线按照时间轴 划分,类似的有 CDQ分治
但线段树分治可以完成一个独有的操作 —— 删除,一些不可以支持删除的数据结构中可以使用,如 李超树,并查集等

我们将时间轴按照线段树的结构分层,形成了 nlogn 个节点,每个点的意义就是 存在时间包含该点区间 的操作数集合,则对于每一个修改,把删除变为修改在一段 时间区间 的操作,可以在线段树上插入,复杂度是 O(logn) 的。

则离线之后我们遍历整颗线段树(按照时间顺序),假设在当前节点,则将该点内的集合进行修改,在退出该点区间时再将 所有操作撤销,撤销是好办的,只需将原节点数据存到栈内即可,但是 不可维护复杂度均摊的数据结构(没有撤销除外)。

注意,支持删除改撤销是线段树分治的 功能,不是线段树分治必须撤销。

3.2 扩展应用

3.2.1 询问时间区间

当询问在一段 时间区间 内时,我们也可以按照时间分治,将询问区间拆分,遍历一遍找出最优答案,若修改是单点的,则可以不需要撤销,清空即可

见例 II

3.3 例题

I P5787 二分图 /【模板】线段树分治

板子,我们可以用 扩展域 并查集实现二分图的判断,因为路径压缩是均摊的,我们需要按秩合并即可。

复杂度 O(nlognlogk)

int n,m,k;
struct node{int fa,siz;};
struct DSU{
	node s[N];
	void build(){for(int i = 1;i <= n + n;i++)s[i] = {i,1};}
	int find(int x){return x == s[x].fa ? x : find(s[x].fa);}
	void merge(int x,int y,vpi &st){
		x = find(x),y = find(y);
		if(x == y)return;
		if(s[x].siz < s[y].siz)swap(x,y);
		st.pb({x,s[x]}),st.pb({y,s[y]});
		s[y].fa = x,s[x].siz += s[y].siz;
	}
	void del(vpi d){//撤销
		for(int i = (int)d.size()-1;i >= 0;i--){
			pi now = d[i];
			s[now.fi] = now.se;
		}
	}
}U;

struct made{int x,y;};
struct segment{
	vector<made>s[N<<2];
	bool vi[N<<2];
	void insert(int p,int l,int r,int L,int R,made x){
		if(L > R)return;
		if(L <= l && r <= R)return s[p].pb(x),void();
		int mid = l + r >> 1;
		if(L <= mid)insert(p<<1,l,mid,L,R,x);
		if(R > mid)insert(p<<1|1,mid+1,r,L,R,x);
	}
	void ask(int p,int l,int r){
		vpi de;
		if(!vi[p]){
			for(made now : s[p]){
				int u = now.x,v = now.y;
				if(U.find(u) == U.find(v)){vi[p] = 1;break;}
				U.merge(u,v + n,de),U.merge(u + n,v,de);
			}
		}
		if(l == r)return vi[p] ? printf("No\n") : printf("Yes\n"),U.del(de),void();
		vi[p<<1] = vi[p<<1|1] = vi[p];
		int mid = l + r >> 1; 
		ask(p<<1,l,mid),ask(p<<1|1,mid+1,r),U.del(de);
	}
}t;


int main(){
	n = read(),m = read(),k = read();
	U.build();
	for(int i = 1;i <= m;i++){
		int x = read(),y = read(),l = read(),r = read();
		t.insert(1,1,k,l+1,r,{x,y});
	}
	t.ask(1,1,k);

	return 0;
}

II P4585 [FJOI2015] 火星商店问题

题意很考验语文功底,是shit,但是题目很好。

题意:修改使一个商店在当天暂时(明天就没了)有一个价值为 w 的商品,询问 d 天内 [l,r] 区间内商店异或最大值。

首先子问题可以 可持久化 Trie 做,对于询问,我们拆分时间,而且不用撤销,直接分治即可。

代码

III P4219 [BJOI2014] 大融合

最后是一棵树,即求当前时刻断开 (x,y) 这条边两个联通块大小的乘积,我不会 LCT,只能另寻他法。

删除,我们可以想到用线段树分治,加上可撤销并查集,对于每条边,即存在的区间即为 除去询问 的所有区间,总区间个数是 O(n) 的,插入修改,然后按照时间顺序遍历线段树,撤销并查集即可,复杂度 O(nlog2n)

代码

IV P5416 [CTSC2016] 时空旅行

一道好题。

这是一个树形结构,我们可以先拍成 dfn 序,然后对于每个星球,本质上就是在其子树内区间再 删去一些小子树 所构成的一些区间,因为最多 n 个操作,所以区间最多 O(n) 个,线段树分治,把这些区间插入,然后我们考虑如何求答案。

y,z 是没用的,最终答案即为 (x0x)2+c,拆开得 2x0x+x02+x2+c

我们考虑斜率优化,在看这个式子 s=2x0x+x02+x2+c,移项得 x2+c=2x0xx02+s,即点 (x,x2+c) 斜率为 2x0,求最小截距,维护下凸包即可,若二分则复杂度是 O(nlog2n),只需将 x 与斜率 x0 都从小到大排序,我们可以利用单调栈达成 O(nlogn) 的复杂度。

代码

3.4 参考文章

线段树分治总结 - foreverlasting

线段树分治 - Autoint's

线段树与离线询问 - OI-wiki

一些常用的数据结构维护手法 - command_block

4. Segment Tree Beats

又称吉司机线段树,简称 STB。

当存在一些形如 使区间 [l,r] 中的数 aimin(ai,k)aimax(ai,k)

吉司机线段数是势能线段树,复杂度约 O(nlog2n)

4.1 算法简介

上述操作我们称其为 区间最值操作

我们考虑简单的操作,有区间求和与区间最值操作,我们以区间取 min(ai,k) 为例。

可以发现该操作只会影响 >k 的节点,所以我们考虑在线段树节点中存储 mx 最大值,se 次大值,以及 cnt 最大值出现的次数。这样我们分类讨论:

  • kmx,则该操作不会对该区间有任何影响。
  • mx>k>se,的最大值会改变,且区间和会减去 cnt×(mxk),记下懒标记即可。
  • kse,我们无法简单的处理,向下递归即可。

可以证明,该算法的复杂度是 O(mlogn) 的。

  • 证明: 先咕掉。

4.2 拓展操作

4.2.1 区间加法

加上区间加法操作,我们有两种方法:

  • 运用多种懒标记:(add,k) 表示先加上 add 在与 kmin,即 ai=min(ai+add,k)

    考虑合并两个懒标记 (add1,k1)(add2,k2)=(add1+add2,min(k1+add2,k2)),注意顺序,可以看 0.

  • 一个核心思想:将区间最值问题转化为对值域的区间加减问题

    我们可以发现 4.1 中我们只进行的对最大值的修改,其实就相当于在该区间对最大值进行减 mxk 的操作,所以我们可以将每个区间的数分为 最大值 与其他数,即划分数域,这样我们只需要维护两个不同的区间加懒标记即可。

两种方法都有用途,建议都学。

论文中证明了带有区间加法操作的总复杂度为 O(mlog2n)

4.2.2 与历史最值问题的结合

详见 5.2.2

4.3 例题

I hdu 5306. Gorgeous Sequence

这是模板题,这里没用划分数域的方法。

struct STB{
#define ls p<<1
#define rs p<<1|1
	int mx[M],se[M],cn[M],la[M];//最大 次大 最大值个数 懒标记
	ll sum[M];
	void pushup(int p){
		sum[p] = sum[ls] + sum[rs];
		if(mx[ls] > mx[rs])mx[p] = mx[ls],cn[p] = cn[ls],se[p] = max(mx[rs],se[ls]);
		else if(mx[ls] < mx[rs])mx[p] = mx[rs],cn[p] = cn[rs],se[p] = max(mx[ls],se[rs]);
		else mx[p] = mx[ls],cn[p] = cn[ls] + cn[rs],se[p] = max(se[ls],se[rs]);
	}
	void update(int p,int k){
		if(k < mx[p]){
			sum[p] -= (ll)(mx[p] - k) * cn[p];
			mx[p] = la[p] = k;
		}
	}
	void pushdown(int p){
		if(la[p] != inf)update(ls,la[p]),update(rs,la[p]);
		la[p] = inf;
	}
	void build(int p,int l,int r){
		la[p] = inf;
		if(l == r)return sum[p] = mx[p] = a[l],se[p] = -inf,cn[p] = 1,void();
		int mid = l + r >> 1;
		build(ls,l,mid),build(rs,mid+1,r);
		pushup(p);
	}
	void modify(int p,int l,int r,int L,int R,int k){
		if(k >= mx[p])return;
		if(L <= l && r <= R && k < mx[p] && k > se[p])return update(p,k),void();
		int mid = l + r >> 1;
		pushdown(p);
		if(L <= mid)modify(ls,l,mid,L,R,k);
		if(R > mid)modify(rs,mid+1,r,L,R,k);
		pushup(p);
	}//区间最值操作
	int cal1(int p,int l,int r,int L,int R){
		if(L <= l && r <= R)return mx[p];
		int mid = l + r >> 1;
		pushdown(p); 
		if(R <= mid)return cal1(ls,l,mid,L,R);
		else if(L > mid)return cal1(rs,mid+1,r,L,R);
		else return max(cal1(ls,l,mid,L,R),cal1(rs,mid+1,r,L,R));
	}
	ll cal2(int p,int l,int r,int L,int R){
		if(L <= l && r <= R)return sum[p];
		int mid = l + r >> 1;
		pushdown(p);
		if(R <= mid)return cal2(ls,l,mid,L,R);
		else if(L > mid)return cal2(rs,mid+1,r,L,R);
		else return cal2(ls,l,mid,L,R) + cal2(rs,mid+1,r,L,R);
	}
#undef ls
#undef rs
}t; 

给出一个实现。

II P10639 BZOJ4695 最佳女选手

操作及其的多,我们若用一坨懒标记则会麻烦的不得了,需要用到划分数域的方法。

我们将区间中的数分为 最大值、最小值以及其他值,将区间最值操作改为值域区间加减操作,我们维护三个懒标记,每个节点要存 最大最小值,次大次小值,最大最小值次数,在区间内只存在很少数时会存在数域重叠的情况,会多算答案,需要分类讨论一下,码量很大。

比如最大值等于最小值,次大值等于最小值等。

代码

III P10638 BZOJ4355 Play with sequence

对于操作一,我们可以视为 aimax(aiinf,c),这样我们把前两个操纵归结成了一个,即先区间加,再区间取最值。

对于操作三,我们只需要找到 cnt×[mi==0],即可,类似区间取最值的写法。

复杂度 O(mlog2n)

代码

IV P9631 [ICPC2020 Nanjing R] Just Another Game of Stones

好题,首先操作 1 是 STB,利用异或的性质可以处理。

对于操作 2,我们知道当且仅当所有石堆个数的异或和0 时先手必败,设 s=xori=lraixorx,即我们需要在 [l,r] 中一个石堆 ai 选出 k 个,变为了 ai,使得 sxoraixorai=0,即 ai=aixors

aiaixors,需要满足 aiaixors,可以发现若 s 最高位 1bit,当且仅当 aibit 位为 1 时,符合 aiaixors

  • 证明:很好证,大于 bit 的位数是不变的,若 aibit 位为 0,则异或后一定为 1,最后一定大于 ai

这样我们拆下位,记录区间异或和,以及区间内各二进制位数的数目

复杂度 O(mlognlogV)

代码1(普通懒标记做法)

代码2(划分数域做法)

4.4 参考文章:

吉司机线段树(Segment Tree Beats!)复杂度分析 - Charles Wu

区间最值操作与区间历史最值详解 - 灵梦

区间最值操作与历史最值操作 - 吉如一 2016集训队论文

5. 历史最值问题

对于一个数列,在操作的途中,我们钦定另一个数列 bi 表示 ai历史,例如历史最大/最小值,历史和。

5.1 算法介绍

  • 历史最大值:每次操作后,我们都令 bi=max(bi,ai),称 ba 的历史最大值数组。
  • 历史最小值:每次操作后,我们都令 bi=min(bi,ai),称 ba 的历史最小值数组。
  • 历史和:每次操作后,我们都令 bi=bi+ai,称 ba 的历史和数组。

我们继续从简单的懒标记方法引入。

例题:P4314 CPU 监控

  • 查询区间最大值。
  • 查询区间历史最大值。
  • 区间加,区间赋值。

我们把通常的标记与数据都分为两类,最大值与历史最大值 mxh,加法标记与历史最大加法标记 addh,覆盖标记与历史最大覆盖标记 tagh

考虑标记合并,我们定于顺序为先加后覆盖,所以对于加法标记,若该区间内已经有覆盖标记,可视为将覆盖标记增加。

对于历史最大值,有 (mx,mxh)(add,addh)=(mx+add,max(mxh,mx+addh))(mx,mxh)(tag,tagh)=(tag,max(mxh,tagh))

对于历史标记合并,有:

  • (add1,addh1)(add2,addh2)=(add1+add2,max(addh1,add1+addh2))
  • (tag1,tagh1)(tag2,tagh2)=(tag2,max(tagh1,tagh2))
  • (tag,tagh)(add,addh)=(tag+add,max(tagh,tag+addh))

第三类转移与所定义的标记顺序相关。

总复杂度 O(mlogn)

代码

总结:其实本质上就是暴力的将标记与数据拆分为普通标记与另类标记,在进行合并的时候会更加麻烦。

5.2 扩展应用

5.2.1 与区间最值问题的结合

对于一些问题,不仅有历史操作,还有最值操作。

例题:#164. 【清华集训2015】V

我们定义标记 tag=(add,k) 表示先加后向 kmax,则前三个操作可以归结为标记 (x,0),(x,inf),(inf,x)

然后在拆分为普通标记与历史最大标记,普通标记合并有 (add1,k1)(add2,k2)=(add1+add2,max(k1+add2,k2))

对于历史最大标记,我们可以把该标记作为一个分段函数,合并两个标记即合并连个函数。

image

如图有:(addh1,kh1)(addh2,kh2)=(max(addh1,addh2),max(kh1,kh2))

所以对于标记合并有 (tag1,tagh1)(tag2,tagh2)=(tag1tag2,tagh1(tag1tagh2))

比较难写,建议用结构体维护标记,以及半群转移

复杂度 O(mlogn)

代码

5.2.2 划分数域方法。

一些情况下,历史标记无法合并,这时就不能用普通的懒标记维护,例如区间最值与所查询答案相反

例题:P6242 【模板】线段树 3(区间最值操作、区间历史最值)

该题操作存在区间取最小值,但是询问的是历史最大值

此时我们无法合并历史最大标记,考虑数域划分,将区间最值转化为值域加减问题,我们需要先将值域分为最大值与其他值,再拆分为加法标记与历史最大加法标记,所以我们需要维护 4 个加法标记。

标记合并与上述类似,不在赘述,只需要分类下即可。

复杂度 O(mlog2n)

代码

另一个模板:#169. 【UR #11】元旦老人与数列

5.2.3 无区间最值的历史和

这里只讨论无区间最值的历史和。

有的不会

5.2.3.1 历史最小值的和

即求 i=lrbi,每次操作有 bi=min(bi,ai)

我们定义辅助数组 c,每一时刻都有 ci=aibi

当我们将 ai 加上 k 时,若 bi 不变,则有 ci 也加上 k,否则 ci 会得 0,即 ci=max(ci+k,0)

我们分别维护 ac,做差就可得到 b,复杂度 O(mlog2n)

5.2.3.2 历史最小值的和

即求 i=lrbi,每次操作有 bi=max(bi,ai)

我们定义辅助数组 c,每一时刻都有 ci=aibi,类似的可以作为 ci=min(ci+k,0),复杂度 O(mlog2n)

5.2.3.3 历史和的和

即求 i=lrbi,每次操作有 bi=bi+ai

我们定义辅助数组 c,令 t 表示目前总操作数,每一时刻有 ci=bit×ai,则当给 ai 加上 k 时,有 ci 减去 k×t,这是简单的区间加减操作,复杂度 O(mlogn)

5.2.4 有区间最值的历史和

咕咕咕....

5.3 例题

5.4 参考文章

区间最值操作与区间历史最值详解 - 灵梦

区间最值操作与历史最值操作 - 吉如一 2016集训队论文

1

posted @   oXUo  阅读(157)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
网站统计
点击右上角即可分享
微信分享提示