线段树科技合订本

线段树和矩阵

矩阵不满足交换律,但满足结合律,所以我们可以用线段树维护矩阵乘法和广义矩阵乘法,甚至还能上树剖,这就是 ddp,可以去搜 “动态 dp”,对矩阵进行修改可以更改转移,不难理解。

线段树历史版本和

操作一个序列:

  • \(1.\)\([l,r]\) 加上 \(x\)
  • \(2.\) 区间 \([l,r]\) 生成一次历史版本
  • \(3.\) 查询区间 \([l,r]\) 的历史版本和

维护信息如下:
\(\texttt{res}\):区间历史和
\(\texttt{sum}\):区间和

维护懒标记 \(\texttt{tag}\) 如下:
\(\texttt{tag}\):常规区间加标记(清空)
\(\texttt{tagh}\):历史区间加总和标记(清空)
\(\texttt{cnt}\):区间加次数(清空)

线段树最重要在 \(\texttt{Pushdown}\),也就是标记的合并,怎么办?
假设现在有两个标记队列 \(q_1,q_2\),标记有加上 \(x\) 或者生成 \(x\) 次历史版本。
考虑每个标记对单个数的贡献,贡献为 \(\texttt{(标记的大小)} \times \texttt(标记以后保存历史版本次数)\),贡献的东西就存在 \(\texttt{tagh}\) 里面。

那么简单了,两个标记合并我们考虑跨过标记的贡献,也就是先来的的标记大小和和乘上后来的的历史版本次数。我们认为标记内部的已经贡献完毕了。
那么,最重要的下方标记已经清楚了,代码如下:

#include<bits/stdc++.h>
#define N 100005
#define ll long long
using namespace std;
int n,m;
struct tags{
	ll htag,tag,cnt;
};
struct segtree{
	tags w[N*4];
	ll sum[N*4],res[N*4];
	void Pushup(int x)
	{
		sum[x]=sum[x*2]+sum[x*2+1];
		res[x]=res[x*2]+res[x*2+1];
	}
	void updata(int x,tags v,ll l)
	{
		res[x]+=sum[x]*v.cnt+v.htag*l;
		sum[x]+=l*v.tag;
		w[x].htag+=v.htag+w[x].tag*v.cnt;
		w[x].cnt+=v.cnt;
		w[x].tag+=v.tag;
	}
	void Pushdown(int x,ll l)
	{
		updata(x*2,w[x],l-(l/2));
		updata(x*2+1,w[x],l/2);
		w[x]={0};
	}
	void modify(int l,int r,int L,int R,int x,ll w)
	{
		if(l>R||r<L) return;
		if(l>=L&&r<=R)
		{
			updata(x,(tags){0,w,0},r-l+1);
			return;
		}
		int mid=(l+r)/2;
		Pushdown(x,r-l+1);
		modify(l,mid,L,R,x*2,w);
		modify(mid+1,r,L,R,x*2+1,w);
		Pushup(x);
	}
	ll query(int l,int r,int L,int R,int x)
	{
		if(l>R||r<L) return 0;
		if(l>=L&&r<=R) return res[x];
		int mid=(l+r)/2;
		Pushdown(x,r-l+1);
		return query(l,mid,L,R,x*2)+query(mid+1,r,L,R,x*2+1);
	}
	void save()
	{
		updata(1,{0,0,1},n); 
	}
}tr;
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) 
	{
		int x;
		scanf("%d",&x);
		tr.modify(1,n,i,i,1,x); 
	}
	while(m--)
	{
		tr.save();
		int opr,l,r,w;
		scanf("%d%d%d",&opr,&l,&r);
		if(opr==1) scanf("%d",&w),tr.modify(1,n,l,r,1,w);
		else printf("%lld\n",tr.query(1,n,l,r,1));
	}
	return 0;
}

[NOIP2022 比赛]

简要题意:
给定两个数组 \(a,b\),执行以下操作:
\(1.\) \(\forall i\in[l,r],a_i\leftarrow a_i+w\)
\(2.\) \(\forall i\in[l,r],b_i\leftarrow b_i+w\)
\(3.\) 对全局保存一次历史版本
\(4.\) 询问所有历史版本 \(\sum\limits_{i=l}^r a_ib_i\) 的和
考虑维护以下信息:
\(\texttt{suma}:\) 区间内数组 \(a\) 的和
\(\texttt{sumb}:\) 同上
\(\texttt{sumab}:\) 区间内 \(ab\) 的和
\(\texttt{ans}\): 区间历史版本和

考虑维护以下标记:
\(\texttt{cnt}:\) 保存历史版本次数
\(\texttt{taga}:\) 数组 \(a\) 的加标记
\(\texttt{tagb}:\) 数组 \(b\) 的加标记
\(\texttt{ha}:\) 数组 \(a\) 的历史加标记和
\(\texttt{hb}:\) 数组 \(b\) 的历史加标记和
\(\texttt{hab}:\) 对于每个数区间加的总贡献和

对于信息方面不进行解释。
考虑对于一段原有的信息打上一段新的标记。
假设我们已经处理好标记内部的贡献,现在我们处理标记外的对于答案的贡献。

我们假设式子是 \(\sum(a_i+x)(b_i+y)\)

Part 1 : \(a_i\times b_i\)

最简单的一个,只需用原来的 \(\sum a_i\times \sum b_i\) 乘上新的保存次数即可。

Part 2: \(a_i\times y\)

考虑是求原来标记的 \(\sum a_i\) 和新标记的加的数的积。
换个思路,我们求新标记中被保存的数的和。也就是新标记的数中 \(y\) 的历史标记和。就是第一问的东西了。那么 \(b_i\times x\) 是同理的。

Part 3: \(x\times y\)

这个和原来的标记没啥关系,计算出对于每个数的 \(\sum xy\) 乘下长度就行。

#include<bits/stdc++.h>
#define N 250005
#define ll unsigned long long
using namespace std;
struct vals{
	ll sa,sb,sab,res;
	ll taga,tagb,cnt,ha,hb,hab;
};
struct segtree{
	vals tr[N*4];
	void Pushup(int x)
	{
		tr[x].sa=tr[x*2].sa+tr[x*2+1].sa;
		tr[x].sb=tr[x*2].sb+tr[x*2+1].sb;
		tr[x].sab=tr[x*2].sab+tr[x*2+1].sab;
		tr[x].res=tr[x*2].res+tr[x*2+1].res;
	}
	void Plus(int x,vals w,ll l)
	{
		tr[x].res+=w.cnt*tr[x].sab+tr[x].sa*w.hb+tr[x].sb*w.ha+w.hab*l;
		tr[x].hab+=w.cnt*tr[x].taga*tr[x].tagb+tr[x].taga*w.hb+tr[x].tagb*w.ha+w.hab;
		tr[x].ha+=tr[x].taga*w.cnt+w.ha;
		tr[x].hb+=tr[x].tagb*w.cnt+w.hb;
		tr[x].sab+=tr[x].sa*w.tagb+tr[x].sb*w.taga+w.taga*w.tagb*l;
		tr[x].sa+=l*w.taga;
		tr[x].sb+=l*w.tagb;
		tr[x].taga+=w.taga;
		tr[x].tagb+=w.tagb;
		tr[x].cnt+=w.cnt;
	}
	void Pushdown(int x,ll l)
	{
		Plus(x*2,tr[x],l-l/2);
		Plus(x*2+1,tr[x],l/2);
		tr[x].taga=tr[x].tagb=tr[x].ha=tr[x].hb=tr[x].hab=tr[x].cnt=0;
	}
	void modify(int l,int r,int L,int R,int x,vals w)
	{
		if(l>R||r<L) return;
		if(l>=L&&r<=R)
		{
			Plus(x,w,r-l+1);
			return;
		}
		Pushdown(x,r-l+1);
		int mid=(l+r)/2;
		modify(l,mid,L,R,x*2,w);
		modify(mid+1,r,L,R,x*2+1,w);
		Pushup(x);
	}
	ll query(int l,int r,int L,int R,int x)
	{
		if(l>R||r<L) return 0;
		if(l>=L&&r<=R) return tr[x].res;
		int mid=(l+r)/2;
		Pushdown(x,r-l+1);
		return query(l,mid,L,R,x*2)+query(mid+1,r,L,R,x*2+1);
	}
}tr;
int stka[N],topa,stkb[N],topb;
int n,m,a[N],b[N];
ll res[N];
struct ques{
	int l,id;
};
vector<ques>q[N];
int main()
{
	scanf("%d%d",&n,&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++) scanf("%d",&b[i]);
	scanf("%d",&m);
	for(int i=1;i<=m;i++)
	{
		int l,r;
		scanf("%d%d",&l,&r);
		q[r].push_back((ques){l,i});
	}
	for(int i=1;i<=n;i++)
	{
		while(topa&&a[i]>a[stka[topa]])
		{
			tr.modify(1,n,stka[topa-1]+1,stka[topa],1,(vals){0,0,0,0,-a[stka[topa]],0,0,0,0,0}); 
			topa--;
		}
		tr.modify(1,n,stka[topa]+1,i,1,(vals){0,0,0,0,a[i],0,0,0,0,0});
		stka[++topa]=i;
		while(topb&&b[i]>b[stkb[topb]])
		{
			tr.modify(1,n,stkb[topb-1]+1,stkb[topb],1,(vals){0,0,0,0,0,-b[stkb[topb]],0,0,0,0}); 
			topb--;
		}
		tr.modify(1,n,stkb[topb]+1,i,1,(vals){0,0,0,0,0,b[i],0,0,0,0});
		stkb[++topb]=i;
		tr.modify(1,n,1,n,1,(vals){0,0,0,0,0,0,1,0,0,0});
		for(auto w:q[i]) res[w.id]=tr.query(1,n,w.l,i,1); 
	}
	for(int i=1;i<=m;i++) printf("%llu\n",res[i]);
	return 0;
}

线段树区间翻转/插入/删除

需要广义线段树。不能可持久化。
将操作区间分成三份,分别是 \([1,l-1]\)\([l,r]\)\([l+1,r]\),暴力对中间的树进行操作,操作完对几棵子树建一棵完美线段树,均摊时间复杂度是能在 \(O(\log n)\) 复杂度完成的。节点数恒为 \(2n-1\),需要细节的垃圾节点回收。

平衡树能干的它都能做,除了 LCT。

如果整棵树空了会出现特别严重的错误,包括但不限于 RE,TLE,MLE,WA。
这里给出 [NOI2005] 维护数列 的 code。

#include<bits/stdc++.h>
#define N 500005
#define ll long long
#define ls(x) tr[x].l
#define rs(x) tr[x].r
using namespace std;
int n,m,T;
const int inf=1e9;
struct vals{
	int sum,lmax,rmax,maxx;
	void fill(int x,int l)
	{
		sum=x*l;
		maxx=lmax=rmax=max(x*l,x);
	}
	void rev()
	{
		swap(lmax,rmax);
	}
};
struct tnode{
	int l,r,siz;
	vals w;
	int rev,cov;
};
vals operator *(vals a,vals b)
{
	return (vals){
		a.sum+b.sum,
		max(a.lmax,b.lmax+a.sum),
		max(b.rmax,a.rmax+b.sum),
		max(a.maxx,max(b.maxx,a.rmax+b.lmax))
	};
}
int rt;
struct segtree{
	tnode tr[N*2];
	int buc[N*2],top,tot;
	inline int gnode()
	{
		int id=top?buc[top--]:++tot;
		tr[id]={0};tr[id].cov=inf;
		return id;
	}
	void Pushup(int x)
	{
		tr[x].w=tr[ls(x)].w*tr[rs(x)].w;
		tr[x].siz=tr[ls(x)].siz+tr[rs(x)].siz;
	}
	void Pushdown(int x)
	{
		if(tr[x].rev)
		{
			tr[ls(x)].rev^=1;
			tr[rs(x)].rev^=1;
			tr[ls(x)].w.rev();
			tr[rs(x)].w.rev();
			swap(ls(ls(x)),rs(ls(x)));
			swap(ls(rs(x)),rs(rs(x)));
			tr[x].rev=0;
		}
		if(tr[x].cov!=inf)
		{
			tr[ls(x)].w.fill(tr[x].cov,tr[ls(x)].siz);
			tr[rs(x)].w.fill(tr[x].cov,tr[rs(x)].siz);
			tr[ls(x)].cov=tr[rs(x)].cov=tr[x].cov;
			tr[x].cov=inf;
		}
	}
	int merges(int l,int r)
	{
		int x=gnode();
		ls(x)=l,rs(x)=r;
		Pushup(x);
		return x;
	}
	int bl[N],bm[N],br[N],dl,dm,dr;
	void split(int u,int l,int r,int L,int R)
	{
		if(r<L){bl[++dl]=u;return;}
		if(l>R){br[++dr]=u;return;}
		if(l>=L&&r<=R){bm[++dm]=u;return;}
		int mid=l-1+tr[ls(u)].siz;
		Pushdown(u);
		split(ls(u),l,mid,L,R);
		split(rs(u),mid+1,r,L,R);
		buc[++top]=u;
	}
	int w,p[N];
	void mergeall()
	{
		while(w>1)
		{
			for(int i=2;i<=w;i+=2)
				p[i/2]=merges(p[i-1],p[i]);
			if(w&1) p[(w+1)/2]=p[w];
			w=(w+1)/2;
		}
		rt=p[1];
	}
	void dfs(int now)
	{
		if(!now) return;
		buc[++top]=now;
		dfs(ls(now)),dfs(rs(now));
	}
	void del(int l,int r)
	{
		dl=dm=dr=w=0;
		split(rt,1,n,l,r);
		for(int i=1;i<=dl;i++) p[++w]=bl[i];
		for(int i=1;i<=dm;i++) dfs(bm[i]);
		for(int i=1;i<=dr;i++) p[++w]=br[i];
		mergeall();
		n-=r-l+1;
	}
	void reverse(int l,int r)
	{
		dl=dm=dr=w=0;
		split(rt,1,n,l,r);
		for(int i=1;i<=dl;i++) p[++w]=bl[i];
		for(int i=dm;i>=1;i--) 
		{
			p[++w]=bm[i];
			swap(ls(bm[i]),rs(bm[i]));
			tr[bm[i]].rev^=1;
			tr[bm[i]].w.rev();
		}
		for(int i=1;i<=dr;i++) p[++w]=br[i];
		mergeall();
	}
	void ins(int *a,int m,int pos)
	{
		dl=dm=dr=w=0;	
		for(int i=1;i<=m;i++) scanf("%d",&a[i]);
		split(rt,1,n,pos,pos);
		for(int i=1;i<=dl;i++) p[++w]=bl[i];
		for(int i=1;i<=dm;i++) p[++w]=bm[i];
		for(int i=1;i<=m;i++) 
		{
			int x=gnode();
			tr[x].w.fill(a[i],1);
			tr[x].siz=1; 
			p[++w]=x;
		}
		for(int i=1;i<=dr;i++) p[++w]=br[i];
		mergeall();
		n+=m;
	}
	void modify(int u,int l,int r,int L,int R,int v)
	{
		if(l>R||r<L) return;
		if(l>=L&&r<=R)
		{
			tr[u].cov=v;
			tr[u].w.fill(v,r-l+1); 
			return;
		}
		int mid=l+tr[ls(u)].siz-1;
		Pushdown(u);
		modify(ls(u),l,mid,L,R,v);
		modify(rs(u),mid+1,r,L,R,v);
		Pushup(u);
	}
	int query(int u,int l,int r,int L,int R)
	{
		if(l>R||r<L) return 0;
		if(l>=L&&r<=R) return tr[u].w.sum;
		int mid=l+tr[ls(u)].siz-1;
		Pushdown(u);
		return query(ls(u),l,mid,L,R)+query(rs(u),mid+1,r,L,R);
	}	
}tr;
int a[N];
char opr[12];
int main()
{
	scanf("%d%d",&m,&T);
	tr.ins(a,m,0); 
	while(T--)
	{
		int tot,l,r,w;
		scanf("%s",opr+1);
		if(opr[3]^'X') scanf("%d%d",&l,&tot),r=l+tot-1;
		if(opr[1]=='I') tr.ins(a,tot,l);
		if(opr[1]=='D') tr.del(l,r);
		if(opr[3]=='K') scanf("%d",&w),tr.modify(rt,1,n,l,r,w);
		if(opr[1]=='R') tr.reverse(l,r);
		if(opr[1]=='G') printf("%d\n",tr.query(rt,1,n,l,r));
		if(opr[3]=='X') printf("%d\n",tr.tr[rt].w.maxx);
	}
	return 0;
}

其实叫 无旋自适应Leafy tree

segment beat

咕咕咕

posted @ 2024-08-15 15:33  g1ove  阅读(45)  评论(1编辑  收藏  举报