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