线段树科技合订本

线段树和矩阵

矩阵不满足交换律,但满足结合律,所以我们可以用线段树维护矩阵乘法和广义矩阵乘法,甚至还能上树剖,这就是 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;
}

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

需要广义线段树。不能可持久化。
将操作区间分成三份,分别是 \([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  阅读(17)  评论(1编辑  收藏  举报