线段树另一半家桶

前一半家桶

常年做数据结构的人都目光呆滞,极度自卑,后面忘了。

线段树分裂

就是裂开,咋合的就咋裂开,进而和线段树合并类似地,这个东西用于权值线段树的操作。然后线段树合并和线段树分裂一般同时出现,空间炸炸的,这个时候就要写垃圾桶,就是把没用的节点编号扔到一个栈里头。

	inline int merge(int x,int y,int l,int r){
		if(!x||!y)return x^y;
		if(l==r){
			tree[x].val+=tree[y].val;
			del(y);
			return x;
		}
		int mid=l+r>>1;
		ls(x)=merge(ls(x),ls(y),l,mid);
		rs(x)=merge(rs(x),rs(y),mid+1,r);
		push_up(x);
		del(y);
		return x;
	}
	inline void split(int &x,int &y,int l,int r,int ul,int ur){
		if(r<ul||l>ur)return ;
		if(!x)return ;
		if(l>=ul&&r<=ur){
			y=x;
			x=0;
			return ;
		}
		if(!y)y=newnode();
		int mid=l+r>>1;
		if(ul<=mid)split(ls(x),ls(y),l,mid,ul,ur);
		if(ur>mid)split(rs(x),rs(y),mid+1,r,ul,ur);
		push_up(x);
		push_up(y);
	}

码长这个样子,就是说从 x 树中裂出一个值域为 [l,r] 的小树 y,每次遇到小树范围内的节点就给它抢过来(y=x,x=0)。

这个题就是说每个multiset就是一个 rt,然后你分分合合一下。

排序

就是说一个升降序段就是一个权值线段树,每次排序可能会撞到之前排好序的位置,这个时候就拿set维护一下排好序的区间端点然后直接找,找完分裂。

CF911G

这个还挺一眼的,发现 ai 特别小所以直接开100个线段树维护数组下标,每次把 x 线段树的 [l,r] 下标分裂后合并到 y 线段树上。

线段树优化建图

普遍是容易的。

CF786B

板。

炸弹

处理出每个蛋能影响到的区间,线段树优化连边,然后发现会成环所以跑塔尖缩点。有个假做法是在这张dag上跑计数dp,然而一个点可能有多个出边并在之后汇合到一点,考虑到一个蛋最终能影响到的一定是一个连续段的形式,转而维护scc间能覆盖的最左最右位置即可,这样是可以跑拓扑序bfs的。

PUS

跑一个差分约束,u>v=uv 有环无解,无环为最短路。

但是这里的连边是一坨连向一坨形式的,此时有一个trick就是超级节点,就是开一个新节点 p 使得边集 E={(ui,vj,w)} 变成 E={(ui,p,w)}{(p,vi,w)} 这样边数就从 O(n2) 变成 O(n) 了,然后分别开出入边线段树优化建图(但是这个题一边的点特别少,所以可以开一个线段树,少的暴力连边)。

线段树分治

二分图

先说一下可撤销并查集,这个可以自研,开个栈存一下先前合并过的记录,撤销的时候弹栈就可以了。

这类题(至少目前见到的的)都沾着这个。

然后这个题就是说整一个扩展域并查集,因为根据二分图的定义相邻两点的颜色肯定不同,如果连边发现getf到一个点了就no。

线段树分治使用线段树维护时间点的,因为这个题每条边只存在一段时间,可撤销并查集又是按进出栈顺序搞的,所以要给这些边钦定一个不影响答案正确性的处理顺序。

有神人提出用线段树考虑,先给每个边存在的区间在线段树上push进对应点的存储链表中然后对线段树进行遍历,这样的话每当加入一个线段树节点时就allin,离开一个线段树节点时就撤销,l=r 时现在并查集的状态就是答案状态了。最重要的好处是,因为线段树树高 O(logn) 所以可撤销并查集堆桟的复杂度是确定的,并且根据线段树区间加的性质所有待分治的元素个数应该是 O(nlogn) 级别的。

大融合

可以拿线段树合并做,说一下线段树分治做法。询问答案就是这条边两边的联通块大小之积,那不妨询问这条边的时候就把边断掉然后查两侧联通块大小,那这个就很适配线段树分治+可撤销并查集了,一条边 E=(u,v)t0 时刻加入并于 t1...m 被查询,那可以认为它在线段树上的存在时段就是

tm+1=q+1

time=(i=1m[ti+1,ti+11])[t0,t11]

瞎搞一下就做出来了。

CF603E

遇到题目中奇怪的限制应该先手玩一下有没有什么性质,比如这个题就是说如果所有点都是奇数度的话整张图应该全为偶数大小的联通块。这样的话可以把有解判断出来。

然后继续看因为边是只加不删的,所以答案一定单调不增,进而就是说,每条边能给答案贡献的区间应该是一个自它加入开始并在某一时刻停止的连续段。然而这个连续段的详细信息是未知的。

考虑问题本质是一个最优化的形式,可以把边权排序然后从小到大扫,因为最后肯定是所有边都在的,这时可以直接贪心跑克鲁斯卡状物,不妨从后往前考虑,一开始(最后时刻)的最优边集会随着时间的前移而缺失,缺失的时间是容易得到的——即它在输入中被加入的时间,在这之前是缺失的——这时后移kruscal的指针直到图中全为偶联通块为止即可得到答案。

并且会发现此时一条边的存在时间也确定了:就是这条边在输入中的加入时间到kruscal指针移下它的那个时间。题目就解决了。

这种方法据说叫半在线的线段树分治,就是先离线出一部分确定的,分治过程中继续更新答案。

注意这时分治应该是先右再左。

CF576E

学校oj偶遇神秘毒瘤,卡时卡空强如怪物,拼尽全力也无法战胜。

确实没有战胜,因为卡不过空间,卡过去了时间又炸了,遂放弃。

根据板子的思路,就是说维护50个扩展域并查集即可。

但是这个题有个比较蛋疼的点在于《只有当执行后仍然合法才会执行本次操作》所以离线直接就死了,想一想上一题的思路看看能不能继续半在线。

发现是可以的,就是说用一个链表维护边的存在段,但是不事先把它们插到线段树中,等分治到一个 l=r 时扫描这个点的这些边,这时发现可以加那肯定就是可以加了,并且可以确定的是在这条边被再次输入之前会一直留在线段树中,这时用当时维护的链表在这一段添加这条边的贡献就可以了。

upd.注意到星光表示可以改成按秩合并优化先前t掉的码,有时间再说。

CF1140F

这个说是一种常用(?)trick,就是把边看成点把点看成边边看成点,这样的话会发现一次造点就是连接两个边点,一次扩展就是连接所有的行点列点,最后答案就是行列点的乘积。

线段树二分

线段树长得就很二分,这样可以把一些看起来唐唐的 O(nlog2n) 变成看起来牛牛的 O(nlogn)。但是这块没啥题。

Siano

有一个容易得到但是我没得到的性质就是说长得快的在一个切割的时间段一定不低,可以分讨一下 aj>ai,如果 ij 都切了那就是一样高,j 切了 i 没切那肯定 j=b,ibi 切了 j 没切是不存在的,能发生这种情况说明 j 先前被砍到了一个以它的速度不能弥补的高度差(并且j更矮),但是 j 是一定长的快的,所以那一次 i 也一定被砍了,所以就有问题了。

这样的话按生长速度重排一下。每次先打一个生长tag,这样一定有一个后缀(前缀)段是一起高于b 的,二分出这个段然后贡献,覆盖即可。

永无乡

这不是我们线段树合并的梗吗。确实没太明白哪要二分,可能是kth吧,总之很牵强。

追逐游戏

之前模拟赛的一道题,想了一下就是线段树二分。

把每次的路径分讨一下,如果追的人的起点和路径lca上的这条路径有被追的人的起点说明这个人是追不上的,这个好算。剩下情况相当于是相遇问题,这个不好算,但是最优路径是确定的所以相遇时间就能算出来,相当于是求一条路径上第 tim 个走到的点,正反跳重链然后剩下一小段线段树二分。合计时间复杂度大概是 O(nlog2n)

树套树

直接放码。

#include<bits/stdc++.h>
#define int long long
#define MAXN 50005
#define N 10000005
using namespace std;
mt19937 rnd(time(0));
int n,q;
int a[MAXN];
const int inf=2147483647;
namespace FHQ{		
	struct TREE{
		int val,siz;
		int key,lson,rson;
	}tree[N];
	int tot;
	struct FHQ_Treap{
		#define ls(p) tree[p].lson
		#define rs(p) tree[p].rson
		int rt;
		inline int newnode(int val){
			++tot;
			ls(tot)=rs(tot)=0;
			tree[tot].siz=1;
			tree[tot].val=val;
			tree[tot].key=rnd();
			return tot;
		}
		inline void update(int p){
			tree[p].siz=tree[ls(p)].siz+tree[rs(p)].siz+1;
		}
		inline int merge(int x,int y){
			if(!x||!y)return x^y;
			if(tree[x].key<tree[y].key){
				rs(x)=merge(rs(x),y);
				update(x);
				return x;
			}
			else {
				ls(y)=merge(x,ls(y));
				update(y);
				return y;
			}
		}
		inline void split(int p,int val,int &x,int &y){
			if(!p){
				x=y=0;
				return ;
			}
			if(tree[p].val>val){
				y=p;
				split(ls(p),val,x,ls(y));
			}
			else{
				x=p;
				split(rs(p),val,rs(x),y);
			}
			update(p);
		}
		int x,y,z;
		inline void insert(int val){
			split(rt,val,x,y);
			rt=merge(x,merge(newnode(val),y));
		}
		inline void del(int val){
			split(rt,val,x,z);
			split(x,val-1,x,y);
			y=merge(ls(y),rs(y));
			rt=merge(x,merge(y,z));
		}
		inline int rnk(int val){
			split(rt,val-1,x,y);
			int res=tree[x].siz+1;
			rt=merge(x,y);
			return res;
		}
		inline int kth(int p,int k){
			if(k<=tree[ls(p)].siz)return kth(ls(p),k);
			if(k==tree[ls(p)].siz+1)return tree[p].val;
			return kth(rs(p),k-tree[ls(p)].siz-1);
		}
		inline int pre(int val){
			int res=0;
			split(rt,val-1,x,y);
			if(tree[x].siz)res=kth(x,tree[x].siz);
			else res=-inf;
			rt=merge(x,y);
			return res;
		}
		inline int nxt(int val){
			int res=0;
			split(rt,val,x,y);
			if(tree[y].siz)res=kth(y,1);
			else res=inf;
			rt=merge(x,y);
			return res;
		}
		inline void build(int l,int r){
			for(int i=l;i<=r;i++)insert(a[i]);
		}
		#undef ls(p)
		#undef rs(p)
	}T[MAXN<<2];
}
using FHQ::T;
struct Segment_Tree{
	int rt[MAXN<<2];
	#define ls(p) p<<1
	#define rs(p) p<<1|1
	inline void build(int l,int r,int p){
		T[p].build(l,r);
		if(l==r)return ;
		int mid=l+r>>1;
		build(l,mid,ls(p));
		build(mid+1,r,rs(p));
	}
	inline int Rnk(int l,int r,int ul,int ur,int val,int p){
		if(r<ul||l>ur)return 0;
		if(l>=ul&&r<=ur){
			return T[p].rnk(val)-1;
		}
		int mid=l+r>>1;
		return Rnk(l,mid,ul,ur,val,ls(p))+Rnk(mid+1,r,ul,ur,val,rs(p));
	}
	inline int Kth(int ul,int ur,int k){
		int l=0,r=1e8,res=-1;
		while(r>=l){
			int mid=l+r>>1;
			if(Rnk(1,n,ul,ur,mid,1)<=k-1)res=mid,l=mid+1;
			else r=mid-1;
		}
		return res;
	}
	inline void Modify(int l,int r,int x,int k,int p){
		T[p].del(a[x]);
		T[p].insert(k);
		if(l==r)return ;
		int mid=l+r>>1;
		if(x<=mid)Modify(l,mid,x,k,ls(p));
		else Modify(mid+1,r,x,k,rs(p));
	}
	inline int Pre(int l,int r,int ul,int ur,int val,int p){
		if(l>ur||r<ul)return -inf;
		if(l>=ul&&r<=ur)return T[p].pre(val);
		int mid=l+r>>1;
		return max(Pre(l,mid,ul,ur,val,ls(p)),Pre(mid+1,r,ul,ur,val,rs(p)));
	}
	inline int Nxt(int l,int r,int ul,int ur,int val,int p){
		if(l>ur||r<ul)return inf;
		if(l>=ul&&r<=ur)return T[p].nxt(val);
		int mid=l+r>>1;
		return min(Nxt(l,mid,ul,ur,val,ls(p)),Nxt(mid+1,r,ul,ur,val,rs(p)));
	}
}ST;
signed main(){
	scanf("%lld%lld",&n,&q);
	for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
	ST.build(1,n,1);
	for(int i=1,opt,l,r,k;i<=q;i++){
		scanf("%lld",&opt);
		if(opt==1){
			scanf("%lld%lld%lld",&l,&r,&k);
			printf("%lld\n",ST.Rnk(1,n,l,r,k,1)+1);
		}
		if(opt==2){
			scanf("%lld%lld%lld",&l,&r,&k);
			printf("%lld\n",ST.Kth(l,r,k));
		}
		if(opt==3){
			scanf("%lld%lld",&l,&k);
			ST.Modify(1,n,l,k,1);
			a[l]=k;
		}
		if(opt==4){
			scanf("%lld%lld%lld",&l,&r,&k);
			printf("%lld\n",ST.Pre(1,n,l,r,k,1));
		}
		if(opt==5){
			scanf("%lld%lld%lld",&l,&r,&k);
			printf("%lld\n",ST.Nxt(1,n,l,r,k,1));
		}
	}
	return 0;
}

以后遇到题了再写。

Segment Tree Beats

是一种基于势能分析确保复杂度的线段树方法,经典应用是实现历史相关。

线段树3

比如说这道题,会发现最值操作和历史最值都会破坏复杂度,因为lazytag是靠“访问才下放”的原理保证复杂度的,但是为了实现历史操作tag需要随时下放。

吉老师提出了一种很强的方法。对于区间历史最值,就是说,每个tag在线段树上存在一个“标记-下放”的生存周期,在这个生存周期中只要不进行访问所有的tag都会在当前tag上进行堆叠。进而地,考虑对一个周期内的tag维护它们的历史信息,维护两个tag:nowtag和xtag表示当前的tag和历史最大tag,每次更新nowtag的时候就去顺带更新xtag,xtag就会在下次访问时作为更新历史最大值的贡献。

再说区间最值操作,发现想要维护这一操作的正确性就不能笼统地覆盖(因为不知道区间和的损失),但是区间内的元素确实是参差的。不过可以发现的是,随着递归的进行,当前区间内的最大值总会占满这个区间(到叶子,或者中途),或者说当前要最值操作的这个数影响到的值是唯一的(也就是最大值),这个时候维护区间和之类是容易的。

所以考虑维护一个区间的最大和次大值,以及最大值的数量,最值操作时递归直到修改值 val 正好介于区间最大值和次大值之间(大于最大值就不用跑了),此时操作退化为将该区间的已知个数个最大值替换成 val

这个复杂度是双log的,但是我不会证。

还没完,仔细想这一操作对tag的影响,如果当前修改的最大值区间已经有tag了则对它进行覆盖操作还会影响它的tag,而且仅仅是影响最大值。所以考虑维护四个tag:当前区间内的最大值tag,非最大值tag,最大值的历史tag,非最大值的历史tag,下放tag的时候进行分讨:如果大区间的最大值是小区间提供的,则分别下传最大和非最大值tag,否则相当于只下传非最大值tag。

具体细节得看代码。

#include<bits/stdc++.h>
#define MAXN 500005
#define ll long long
using namespace std;
int n,q;
const int inf=2e9;
struct Segment_Tree{
	#define ls(p) p<<1
	#define rs(p) p<<1|1
	struct TREE{
		int l,r;
		ll sum;//区间和
		int amax,bmax,cnt,pmax;//当前最大值,历史最大值,当前最大值个数,次大值
		int xtag,ytag,ztag,wtag;//当前最大值,当前非最大值,历史最大值,历史非最大值tag
	}tree[MAXN<<2];
	inline void push_up(int p){
		tree[p].sum=tree[ls(p)].sum+tree[rs(p)].sum;
		tree[p].amax=max(tree[ls(p)].amax,tree[rs(p)].amax);
		tree[p].bmax=max(tree[ls(p)].bmax,tree[rs(p)].bmax);
		if(tree[ls(p)].amax<tree[rs(p)].amax){
			tree[p].cnt=tree[rs(p)].cnt;
			tree[p].pmax=max(tree[ls(p)].amax,tree[rs(p)].pmax);
		}
		else if(tree[ls(p)].amax>tree[rs(p)].amax){
			tree[p].cnt=tree[ls(p)].cnt;
			tree[p].pmax=max(tree[rs(p)].amax,tree[ls(p)].pmax);
		}
		else{
			tree[p].cnt=tree[ls(p)].cnt+tree[rs(p)].cnt;
			tree[p].pmax=max(tree[ls(p)].pmax,tree[rs(p)].pmax);
		}
	}
	inline void build(int l,int r,int p){
		tree[p].l=l,tree[p].r=r;
		if(l==r){
			int v;
			scanf("%d",&v);
			tree[p].amax=tree[p].bmax=tree[p].sum=v;
			tree[p].cnt=1;
			tree[p].pmax=-inf;
			return ;
		}
		int mid=l+r>>1;
		build(l,mid,ls(p));
		build(mid+1,r,rs(p));
		push_up(p); 
	}
	inline void change(int k1,int k2,int k3,int k4,int p){
		tree[p].sum+=(ll)k1*tree[p].cnt+(ll)k2*(tree[p].r-tree[p].l+1-tree[p].cnt);//除了cnt个最大值用k1(可能最大值标记)剩下都用非最大值标记k2。
		tree[p].bmax=max(tree[p].bmax,tree[p].amax+k3);//维护历史最值
		tree[p].amax+=k1;//当前值用当前tag
		if(tree[p].pmax!=-inf)tree[p].pmax+=k2;//次大值用次大当前tag
		tree[p].ztag=max(tree[p].ztag,tree[p].xtag+k3);
		tree[p].wtag=max(tree[p].wtag,tree[p].ytag+k4);//历史tag
		tree[p].xtag+=k1,tree[p].ytag+=k2;
	}
	inline void spread(int p){
		int xv=max(tree[ls(p)].amax,tree[rs(p)].amax);
		if(xv==tree[ls(p)].amax)change(tree[p].xtag,tree[p].ytag,
		tree[p].ztag,tree[p].wtag,ls(p));
		else change(tree[p].ytag,tree[p].ytag,tree[p].wtag,tree[p].wtag,ls(p));
//分讨一下,如果最大值是这个小区间提供的就把最大值tag放下去,否则都用非最大值tag放下去
		if(xv==tree[rs(p)].amax)change(tree[p].xtag,tree[p].ytag,
		tree[p].ztag,tree[p].wtag,rs(p));
		else change(tree[p].ytag,tree[p].ytag,tree[p].wtag,tree[p].wtag,rs(p));
		tree[p].xtag=tree[p].ytag=tree[p].ztag=tree[p].wtag=0;
	}
	inline void plus(int l,int r,int k,int p){
		if(tree[p].l>=l&&tree[p].r<=r){
			tree[p].sum+=(ll)k*(tree[p].r-tree[p].l+1);
			tree[p].amax+=k;
			tree[p].bmax=max(tree[p].amax,tree[p].bmax);
			if(tree[p].pmax!=-inf)tree[p].pmax+=k;
			tree[p].xtag+=k,tree[p].ytag+=k;
			tree[p].ztag=max(tree[p].ztag,tree[p].xtag);
			tree[p].wtag=max(tree[p].wtag,tree[p].ytag);
			return ;
		}//区间加操作,很好理解但是注意细节。
		spread(p);
		int mid=tree[p].l+tree[p].r>>1;
		if(l<=mid)plus(l,r,k,ls(p));
		if(r>mid)plus(l,r,k,rs(p));
		push_up(p);
	}
	inline void modify(int l,int r,int k,int p){
		if(k>=tree[p].amax)return ;
		if(tree[p].l>=l&&tree[p].r<=r&&tree[p].pmax<k){//递归直到当前区间待修改元素全为最大值
			int d=tree[p].amax-k;//这个是修改量,可以认为是一次区间减
			tree[p].sum-=(ll)tree[p].cnt*d;
			tree[p].amax=k;
			tree[p].xtag-=d;//tag也得改,因为是区间减
			return ;
		}
		spread(p);
		int mid=tree[p].l+tree[p].r>>1;
		if(l<=mid)modify(l,r,k,ls(p));
		if(r>mid)modify(l,r,k,rs(p));
		push_up(p);
	}
	inline ll squery(int l,int r,int p){
		if(l>r)return 0;
		if(tree[p].l>=l&&tree[p].r<=r)return tree[p].sum;
		spread(p);
		int mid=tree[p].l+tree[p].r>>1;
		ll res=0;
		if(l<=mid)res+=squery(l,r,ls(p));
		if(r>mid)res+=squery(l,r,rs(p));
		return res;
	}
	inline int aquery(int l,int r,int p){
		if(l>r)return 0;
		if(tree[p].l>=l&&tree[p].r<=r)return tree[p].amax;
		spread(p);
		int mid=tree[p].l+tree[p].r>>1;
		int res=-inf;
		if(l<=mid)res=max(res,aquery(l,r,ls(p)));
		if(r>mid)res=max(res,aquery(l,r,rs(p)));
		return res;
	}
	inline int bquery(int l,int r,int p){
		if(l>r)return 0;
		if(tree[p].l>=l&&tree[p].r<=r)return tree[p].bmax;
		spread(p);
		int mid=tree[p].l+tree[p].r>>1;
		int res=-inf;
		if(l<=mid)res=max(res,bquery(l,r,ls(p)));
		if(r>mid)res=max(res,bquery(l,r,rs(p)));
		return res;
	}
}ST;
signed main(){
	scanf("%d%d",&n,&q);
	ST.build(1,n,1);
	for(int i=1,opt,l,r,k;i<=q;i++){
		scanf("%d%d%d",&opt,&l,&r);
		if(opt==1){
			scanf("%d",&k);
			ST.plus(l,r,k,1);
		}
		else if(opt==2){
			scanf("%d",&k);
			ST.modify(l,r,k,1);
		}
		else if(opt==3)printf("%lld\n",ST.squery(l,r,1));
		else if(opt==4)printf("%d\n",ST.aquery(l,r,1));
		else printf("%d\n",ST.bquery(l,r,1));
	}
	return 0;
}

这种分段维护tag的思想可以用来解决其他一些问题。

CPU监控

这个题要实现的就是区间极值,区间历史极值,区间加和区间修改。仍然考虑历史极值的维护,用四个tag分别维护当前和历史的加tag和覆盖tag。发现区间加和区间覆盖tag是难以同时存在的,仔细想一下:如果先前有一个覆盖tag,而后加入了区间加tag,则等价于进行了多次区间覆盖。所以没覆盖的时候直接加,有覆盖的时候就那加tag贡献到覆盖tag,进而优先处理加tag。

历史覆盖tag和历史加tag一样的,取最大值就行。

	struct TREE{
		int l,r;
		int amax,bmax;
		int xtag,ytag,ztag,wtag;//加和历史加tag,覆盖和历史覆盖tag
	}tree[MAXN<<2];
	inline void push_up(int p){
		tree[p].amax=max(tree[ls(p)].amax,tree[rs(p)].amax);
		tree[p].bmax=max(tree[ls(p)].bmax,tree[rs(p)].bmax);
	}	
	inline void build(int l,int r,int p){
		tree[p].ztag=tree[p].wtag=-inf;
		tree[p].l=l,tree[p].r=r;
		if(l==r){
			int v;
			scanf("%lld",&v);
			tree[p].amax=tree[p].bmax=v;
			return ;
		}
		int mid=l+r>>1;
		build(l,mid,ls(p));
		build(mid+1,r,rs(p));
		push_up(p);
	}
	inline void Add(int p,int k1,int k2){
		tree[p].bmax=max(tree[p].bmax,tree[p].amax+k2);
		tree[p].amax+=k1;
		if(tree[p].ztag==-inf){
			tree[p].ytag=max(tree[p].ytag,tree[p].xtag+k2);
			tree[p].xtag+=k1;
		}
		else{
			tree[p].wtag=max(tree[p].wtag,tree[p].ztag+k2);
			tree[p].ztag+=k1;
		}
	}
	inline void Fil(int p,int k1,int k2){
		tree[p].bmax=max(tree[p].bmax,k2);
		tree[p].amax=k1;
		tree[p].wtag=max(tree[p].wtag,k2);
		tree[p].ztag=k1;
	}
	inline void spread(int p){
		if(tree[p].xtag||tree[p].ytag){
			Add(ls(p),tree[p].xtag,tree[p].ytag);
			Add(rs(p),tree[p].xtag,tree[p].ytag);
			tree[p].xtag=tree[p].ytag=0;
		}
		if(tree[p].ztag!=-inf||tree[p].wtag!=-inf){
			Fil(ls(p),tree[p].ztag,tree[p].wtag);
			Fil(rs(p),tree[p].ztag,tree[p].wtag);
			tree[p].ztag=tree[p].wtag=-inf;
		}
	}
	inline void plus(int l,int r,int k,int p){
		if(tree[p].l>=l&&tree[p].r<=r){
			Add(p,k,max(k,0ll));
			return ;
		}
		spread(p);
		int mid=tree[p].l+tree[p].r>>1;
		if(l<=mid)plus(l,r,k,ls(p));
		if(r>mid)plus(l,r,k,rs(p));
		push_up(p);
	}
	inline void modify(int l,int r,int k,int p){
		if(tree[p].l>=l&&tree[p].r<=r){
			Fil(p,k,k);
			return ;
		}
		spread(p);
		int mid=tree[p].l+tree[p].r>>1;
		if(l<=mid)modify(l,r,k,ls(p));
		if(r>mid)modify(l,r,k,rs(p));
		push_up(p);
	}

最佳女选手

板子的强化版,可以检测到底想没想明白这些tag(虽然我自己不看tj没调出来qwq)。

仍然类似地维护极大极小值和它们分别的个数,和次大次小值,以及用于两个极值和一般值的tag。

得好好想想最值操作怎么影响对立值的tag!比如区间最大值操作会影响到区间最小值(递归到一定程度时),这时候区间最小tag要被影响,反之是区间最大tag被影响,区间加则是影响所有tag,其实没有很难。

	struct TREE{
		int l,r;
		ll sum;
		int gcnt,wcnt,aval,bval,cval,dval;//极大极小个数与值 ,次大次小 
		int xtag,ytag,ztag;//极大极小一般标记 
	}tree[MAXN<<2];
	inline void push_up(int p){
		tree[p].sum=tree[ls(p)].sum+tree[rs(p)].sum;
		tree[p].aval=max(tree[ls(p)].aval,tree[rs(p)].aval);
		tree[p].bval=min(tree[ls(p)].bval,tree[rs(p)].bval);
		if(tree[ls(p)].aval>tree[rs(p)].aval){
			tree[p].gcnt=tree[ls(p)].gcnt;
			tree[p].cval=max(tree[ls(p)].cval,tree[rs(p)].aval);
		}
		else if(tree[ls(p)].aval<tree[rs(p)].aval){
			tree[p].gcnt=tree[rs(p)].gcnt;
			tree[p].cval=max(tree[ls(p)].aval,tree[rs(p)].cval);
		}
		else{
			tree[p].gcnt=tree[ls(p)].gcnt+tree[rs(p)].gcnt;
			tree[p].cval=max(tree[ls(p)].cval,tree[rs(p)].cval);
		}
		if(tree[ls(p)].bval<tree[rs(p)].bval){
			tree[p].wcnt=tree[ls(p)].wcnt;
			tree[p].dval=min(tree[ls(p)].dval,tree[rs(p)].bval);
		}
		else if(tree[ls(p)].bval>tree[rs(p)].bval){
			tree[p].wcnt=tree[rs(p)].wcnt;
			tree[p].dval=min(tree[ls(p)].bval,tree[rs(p)].dval);
		}
		else{
			tree[p].wcnt=tree[ls(p)].wcnt+tree[rs(p)].wcnt;
			tree[p].dval=min(tree[ls(p)].dval,tree[rs(p)].dval);
		}
	}
	inline void build(int l,int r,int p){
		tree[p].l=l,tree[p].r=r;
		tree[p].xtag=-inf,tree[p].ytag=inf;
		if(l==r){
			int v;
			scanf("%lld",&v);
			tree[p].sum=tree[p].aval=tree[p].bval=v;
			tree[p].gcnt=tree[p].wcnt=1;
			tree[p].cval=-inf,tree[p].dval=inf;
			return ;
		}
		int mid=l+r>>1;
		build(l,mid,ls(p));
		build(mid+1,r,rs(p));
		push_up(p);
	}
	inline void schange(int p,int k){
		tree[p].sum+=(ll)k*(tree[p].r-tree[p].l+1);
		tree[p].aval+=k,tree[p].bval+=k;
		if(tree[p].cval!=-inf)tree[p].cval+=k;
		if(tree[p].dval!=inf)tree[p].dval+=k;
		if(tree[p].xtag!=-inf)tree[p].xtag+=k;
		if(tree[p].ytag!=inf)tree[p].ytag+=k;
		tree[p].ztag+=k;
	}
	inline void xchange(int p,int k){
		if(tree[p].bval>=k)return ;
		tree[p].sum+=(ll)tree[p].wcnt*(k-tree[p].bval);
		if(tree[p].cval==tree[p].bval)tree[p].cval=k;
		if(tree[p].aval==tree[p].bval)tree[p].aval=k;
		tree[p].ytag=max(tree[p].ytag,k);
		tree[p].bval=k;
		tree[p].xtag=k;
	}
	inline void nchange(int p,int k){
		if(tree[p].aval<=k)return ;
		tree[p].sum+=(ll)tree[p].gcnt*(k-tree[p].aval);
		if(tree[p].dval==tree[p].aval)tree[p].dval=k;
		if(tree[p].bval==tree[p].aval)tree[p].bval=k;
		tree[p].xtag=min(tree[p].xtag,k);
		tree[p].aval=k;
		tree[p].ytag=k;
	}
	inline void spread(int p){
		if(tree[p].ztag)schange(ls(p),tree[p].ztag),schange(rs(p),tree[p].ztag);
		if(tree[p].xtag!=-inf)xchange(ls(p),tree[p].xtag),xchange(rs(p),tree[p].xtag);
		if(tree[p].ytag!=inf)nchange(ls(p),tree[p].ytag),nchange(rs(p),tree[p].ytag);
		tree[p].ztag=0,tree[p].xtag=-inf,tree[p].ytag=inf;
	}
posted @   Cl41Mi5deeD  阅读(8)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示