线段树综合学习笔记
1. 线段树分裂
【模板】线段树分裂
https://www.gxyzoj.com/d/gxyznoi/p/P223
采用和平衡树一样的方法,先求和,然后按线段树二分的写法即可
点击查看代码
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const int N=2e5+5,M=4e6+5;
int n,m,ls[M],del[M],rs[M],rt[N],tot,cnt=1,dcnt;
ll val[M];
int newnode()
{
if(dcnt) return del[dcnt--];
return ++tot;
}
int update(int p,int l,int r,int x,ll v)
{
if(!p) p=newnode();
if(l==r)
{
val[p]+=v;
return p;
}
int mid=(l+r)>>1;
if(x<=mid) ls[p]=update(ls[p],l,mid,x,v);
else rs[p]=update(rs[p],mid+1,r,x,v);
val[p]=val[ls[p]]+val[rs[p]];
return p;
}
ll query(int p,int l,int r,int ql,int qr)
{
if(!p) return 0;
if(ql<=l&&qr>=r)
{
return val[p];
}
int mid=(l+r)>>1;
ll x=0;
if(ql<=mid) x+=query(ls[p],l,mid,ql,qr);
if(qr>mid) x+=query(rs[p],mid+1,r,ql,qr);
return x;
}
void split(int x,int &y,ll k)
{
if(!x) return;
y=newnode();
ll v=val[ls[x]];
if(k>v) split(rs[x],rs[y],k-v);
else swap(rs[x],rs[y]);
if(k<v) split(ls[x],ls[y],k);
val[y]=val[x]-k;
val[x]=k;
}
void delt(int x)
{
del[++dcnt]=x;
val[x]=ls[x]=rs[x]=0;
}
int merge(int x,int y)
{
if(!x||!y) return x+y;
val[x]+=val[y];
ls[x]=merge(ls[x],ls[y]);
rs[x]=merge(rs[x],rs[y]);
delt(y);
return x;
}
int getans(int p,int l,int r,ll k)
{
if(l==r) return l;
int mid=(l+r)>>1;
if(k<=val[ls[p]]) return getans(ls[p],l,mid,k);
else return getans(rs[p],mid+1,r,k-val[ls[p]]);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);
if(x) rt[1]=update(rt[1],1,n,i,x);
}
while(m--)
{
int opt,p,a=0;
ll l,r;
scanf("%d%d%lld",&opt,&p,&l);
if(opt==0)
{
scanf("%lld",&r);
++cnt;
ll k1=query(rt[p],1,n,1,r),k2=query(rt[p],1,n,l,r);
split(rt[p],rt[cnt],k1-k2);
split(rt[cnt],a,k2);
rt[p]=merge(rt[p],a);
}
if(opt==1)
{
rt[p]=merge(rt[p],rt[l]);
}
if(opt==2)
{
scanf("%lld",&r);
rt[p]=update(rt[p],1,n,r,l);
}
if(opt==3)
{
scanf("%lld",&r);
printf("%lld\n",query(rt[p],1,n,l,r));
}
if(opt==4)
{
if(query(rt[p],1,n,1,n)<l)
{
printf("-1\n");
continue;
}
printf("%d\n",getans(rt[p],1,n,l));
}
}
return 0;
}
[HEOI2016/TJOI2016] 排序
https://www.gxyzoj.com/d/gxyznoi/p/P223
因为排序后段内的数值不会变,所以考虑建值域线段树,这样在一个有序段内可以 \(O(log)\) 找到对应值
接下来看如何处理有序段,因为每次排序后的段都是有序的,所以可以开始每位建一个,然后合并
如果排序端点不是有序段端点,可以将对应的分裂出来
对于所有段,可以记录左右端点和方向,然后另外开一棵线段树记录每个点对应的段
时间复杂度上,因为每次操作至多分裂 2 次,每个树至多被合并一次,复杂度为 \(O((n+m)logn)\)
点击查看代码
#include<cstdio>
#include<algorithm>
#define lid id<<1
#define rid id<<1|1
using namespace std;
const int N=3e5+5,M=3e6+5;
int n,m,cnt,rt[N],val[M],ls[M],rs[M],tot;
int del[M],dcnt,L[N],R[N],fl[N],op[N];
int newnode()
{
if(dcnt) return del[dcnt--];
return ++tot;
}
int update(int p,int l,int r,int x)
{
if(!p) p=newnode();
if(l==r)
{
val[p]++;
return p;
}
int mid=(l+r)>>1;
if(x<=mid) ls[p]=update(ls[p],l,mid,x);
else rs[p]=update(rs[p],mid+1,r,x);
val[p]=val[ls[p]]+val[rs[p]];
return p;
}
void split(int x,int &y,int k)
{
if(!x) return;
y=newnode();
int v=val[ls[x]];
if(k>v) split(rs[x],rs[y],k-v);
else swap(rs[x],rs[y]);
if(k<v) split(ls[x],ls[y],k);
val[y]=val[x]-k;
val[x]=k;
}
void delt(int x)
{
del[++dcnt]=x,val[x]=ls[x]=rs[x]=0;
}
int merge(int x,int y)
{
if(!x||!y) return x+y;
val[x]+=val[y];
ls[x]=merge(ls[x],ls[y]);
rs[x]=merge(rs[x],rs[y]);
delt(y);
return x;
}
int getans(int p,int l,int r,int k)
{
if(l==r) return l;
int mid=(l+r)>>1;
if(val[ls[p]]>=k) return getans(ls[p],l,mid,k);
else return getans(rs[p],mid+1,r,k-val[ls[p]]);
}
struct seg_tr{
int l,r,val;
}tr[M];
void build(int id,int l,int r)
{
tr[id].l=l,tr[id].r=r;
if(l==r)
{
tr[id].val=l;
return;
}
int mid=(l+r)>>1;
build(lid,l,mid);
build(rid,mid+1,r);
}
void change(int id,int l,int r,int x)
{
if(l>r) return;
if(tr[id].l==l&&tr[id].r==r)
{
tr[id].val=x;
return;
}
if(tr[id].val)
tr[lid].val=tr[rid].val=tr[id].val,tr[id].val=0;
int mid=(tr[id].l+tr[id].r)>>1;
if(r<=mid) change(lid,l,r,x);
else if(l>mid) change(rid,l,r,x);
else change(lid,l,mid,x),change(rid,mid+1,r,x);
}
int query(int id,int x)
{
if(tr[id].val) return tr[id].val;
int mid=(tr[id].l+tr[id].r)>>1;
if(x<=mid) return query(lid,x);
else return query(rid,x);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);
rt[i]=update(rt[i],1,n,x);
L[i]=R[i]=i;
}
cnt=n;
build(1,1,n);
while(m--)
{
int opt,l,r;
scanf("%d%d%d",&opt,&l,&r);
int u=query(1,l),v=query(1,r);
if(u==v)
{
int x=R[u]-L[u]+1;
if(op[u])
{
cnt++,split(rt[u],rt[cnt],x-(l-L[u]));
op[cnt]=op[u],L[cnt]=L[u],R[cnt]=l-1,change(1,L[cnt],R[cnt],cnt);
cnt++,split(rt[u],rt[cnt],R[u]-r);
op[cnt]=opt,L[u]=r+1,L[cnt]=l,R[cnt]=r,change(1,l,r,cnt);
}
else
{
cnt++,split(rt[u],rt[cnt],x-(R[u]-r));
op[cnt]=op[u],L[cnt]=r+1,R[cnt]=R[u],change(1,L[cnt],R[cnt],cnt);
cnt++,split(rt[u],rt[cnt],l-L[u]);
op[cnt]=opt,R[u]=l-1,L[cnt]=l,R[cnt]=r,change(1,l,r,cnt);
}
}
else
{
int a=0,b=0,x=R[u]+1;
if(op[u])
{
split(rt[u],a,R[u]-l+1);
swap(a,rt[u]);
}
else split(rt[u],a,l-L[u]);
if(op[v]) split(rt[v],b,R[v]-r);
else
{
split(rt[v],b,r-L[v]+1);
swap(b,rt[v]);
}
R[u]=l-1,L[v]=r+1,rt[++cnt]=merge(a,b);
// printf("1");
while(1)
{
int t=query(1,x);
if(t==v) break;
rt[cnt]=merge(rt[cnt],rt[t]);
x=R[t]+1;
}
change(1,l,r,cnt),L[cnt]=l,R[cnt]=r,op[cnt]=opt;
}
// for(int i=1;i<=n;i++)
// {
// int tmp=query(1,i);
// if(op[tmp]) printf("a%d ",getans(rt[tmp],1,n,R[tmp]-i+1));
// else printf("b%d ",getans(rt[tmp],1,n,i-L[tmp]+1));
// }
// printf("\n");
}
int q;
scanf("%d",&q);
int tmp=query(1,q);
if(op[tmp]) printf("%d",getans(rt[tmp],1,n,R[tmp]-q+1));
else printf("%d",getans(rt[tmp],1,n,q-L[tmp]+1));
return 0;
}
A Simple Task
https://www.gxyzoj.com/d/gxyznoi/p/P224
同上
Mass Change Queries
https://www.gxyzoj.com/d/gxyznoi/p/P911G
按值建树,每次把对应的部分分裂即可
2.线段树优化建图
就是将向区间连的边转化为线段树上的点对点连边,从而减少边的数量
Legacy
https://www.gxyzoj.com/d/gxyznoi/p/P786B
因为一定不走回头路,所以每组边至多被经过一次
此时,线段树优化建图然后跑 dij 即可
点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int M=5e6+5,N=1e5+5;
int n,m,ls[N*10],rs[N*10],tot,a[N],b[N],head[N*10],edgenum,st;
int rt1,rt2;
struct edge{
int to,nxt;
ll val;
}e[M];
void add_edge(int u,int v,int w)
{
// printf("e%d %d %d\n",u,v,w);
e[++edgenum].nxt=head[u];
e[edgenum].to=v;
e[edgenum].val=w;
head[u]=edgenum;
}
int build_in(int p,int l,int r)
{
p=++tot;
if(l==r)
{
a[l]=p;
return p;
}
int mid=(l+r)>>1;
ls[p]=build_in(ls[p],l,mid);
rs[p]=build_in(rs[p],mid+1,r);
add_edge(p,ls[p],0),add_edge(p,rs[p],0);
return p;
}
int build_out(int p,int l,int r)
{
p=++tot;
if(l==r)
{
b[l]=p;
add_edge(a[l],p,0);
return p;
}
int mid=(l+r)>>1;
ls[p]=build_out(ls[p],l,mid);
rs[p]=build_out(rs[p],mid+1,r);
add_edge(ls[p],p,0),add_edge(rs[p],p,0);
return p;
}
void update(int p,int l,int r,int ql,int qr,int x,int y,int fl)
{
if(ql<=l&&qr>=r)
{
if(fl) add_edge(p,x,y);
else add_edge(x,p,y);
return;
}
int mid=(l+r)>>1;
if(ql<=mid) update(ls[p],l,mid,ql,qr,x,y,fl);
if(qr>mid) update(rs[p],mid+1,r,ql,qr,x,y,fl);
}
priority_queue<pair<ll,int> >q;
ll dis[N*10];
void dijkstra(int s)
{
q.push(make_pair(0,s));
for(int i=1;i<=tot;i++)
{
dis[i]=1e18;
}
dis[s]=0;
while(!q.empty())
{
int u=q.top().second;
ll w=-q.top().first;
q.pop();
if(w>dis[u]) continue;
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(w+e[i].val<dis[v])
{
dis[v]=w+e[i].val;
q.push(make_pair(-dis[v],v));
}
}
}
}
int main()
{
scanf("%d%d%d",&n,&m,&st);
rt1=build_in(0,1,n);
rt2=build_out(0,1,n);
while(m--)
{
int opt,u,l,r,w;
scanf("%d%d%d%d",&opt,&u,&l,&r);
if(opt==1)
{
// printf("p%d %d %d",u,l,r);
add_edge(b[u],a[l],r);
}
else
{
scanf("%d",&w);
if(opt==2)
{
update(rt1,1,n,l,r,b[u],w,0);
}
else
{
update(rt2,1,n,l,r,a[u],w,1);
}
}
}
dijkstra(b[st]);
for(int i=1;i<=n;i++)
{
if(dis[b[i]]==1e18) printf("-1 ");
else printf("%lld ",dis[b[i]]);
}
return 0;
}
[SNOI2017] 炸弹
https://www.gxyzoj.com/d/gxyznoi/p/P227
可以将每个点向不连锁反应就能到的点连边,此时缩点后同一强连通分量中的点相互可以到达
因为此时的图是 DAG,不方便 dp,但是可以取最大/最小端点
但是如果暴力连,就会炸,考虑线段树优化
[POI 2015] PUS
https://www.gxyzoj.com/d/gxyznoi/p/P228
显然的,可以从大的向小的连边,将这 k 个点和他们中间的区间去连,这部分可以线段树优化
此时,如果成环,必然不行
但是如果分别连,时空肯定不够,所以考虑建虚点
最后看统计答案,可以按拓扑序处理,如果能达到的最大值小于规定值,就没有答案
3. 线段树分治
二分图 /[模板] 线段树分治
这个东西大致就是把操作按有效时间放到线段树上,然后用可撤销并查集处理每个点的状态
https://www.gxyzoj.com/d/gxyznoi/p/P230
对于二分图的判断,可以使用扩展域并查集
这个东西就是将点拆开,分成正点和反点,正点表示在这个集合,反点表示不在
所以可以将不能同处一个集合的两点交叉连边即可,如果连边前两者的正点在同一集合,一定不行
但是如果按照暴力枚举时间的话,每次删边必然用重新建并查集
但是注意到每条边的存在时间是一个段,考虑弄到线段树上做,下标就是时间
此时,记录每个点需要加的边,可以发现在回溯的时候这些边是可撤销的,所以最后 DFS 跑一遍即可
点击查看代码
#include<bits/stdc++.h>
#define lid id<<1
#define rid id<<1|1
using namespace std;
int n,m,k;
struct seg_tr{
int l,r;
vector<pair<int,int> > v;
}tr[800020];
void build(int id,int l,int r)
{
tr[id].l=l,tr[id].r=r;
if(l==r) return;
int mid=(l+r)>>1;
build(lid,l,mid);
build(rid,mid+1,r);
}
void update(int id,int l,int r,int x,int y)
{
if(l>r) return;
if(tr[id].l==l&&tr[id].r==r)
{
tr[id].v.push_back(make_pair(x,y));
return;
}
int mid=(tr[id].l+tr[id].r)>>1;
if(r<=mid) update(lid,l,r,x,y);
else if(l>mid) update(rid,l,r,x,y);
else update(lid,l,mid,x,y),update(rid,mid+1,r,x,y);
}
struct node{
int x,y,fa,siz;
}st[800005];
int top,f[200005],siz[200005];
int find(int x)
{
if(f[x]!=x) return find(f[x]);
return x;
}
void merge(int u,int v)
{
int x=find(u),y=find(v);
if(siz[x]<siz[y]) swap(x,y),swap(u,v);
top++;
st[top]=(node){x,y,f[y],siz[x]};
f[y]=x;
siz[x]+=siz[y];
}
void split(int t)
{
while(top>t)
{
siz[st[top].x]=st[top].siz;
f[st[top].y]=st[top].fa;
top--;
}
}
void getans(int id,int l,int r)
{
int x=top,fl=0;
for(int i=0;i<tr[id].v.size();i++)
{
int u=tr[id].v[i].first,v=tr[id].v[i].second;
if(find(u)==find(v))
{
fl=1;
for(int j=l;j<=r;j++)
{
printf("No\n");
}
break;
}
merge(u,v+n),merge(v,u+n);
}
if(!fl)
{
if(l==r) printf("Yes\n");
else
{
int mid=(l+r)>>1;
getans(lid,l,mid);
getans(rid,mid+1,r);
}
}
split(x);
}
int main()
{
// freopen("1.txt","r",stdin);
// freopen("2.txt","w",stdout);
scanf("%d%d%d",&n,&m,&k);
build(1,1,k);
for(int i=1;i<=m;i++)
{
int l,r,x,y;
scanf("%d%d%d%d",&x,&y,&l,&r);
// printf("%d %d %d %d %d\n",i,x,y,l,r);
update(1,l+1,r,x,y);
}
// printf("1");
for(int i=1;i<=n*2;i++) f[i]=i,siz[i]=1;
getans(1,1,k);
return 0;
}
[BJOI2014] 大融合
https://www.gxyzoj.com/d/gxyznoi/p/P230
可以将每个操作当作一个时刻,此时,如果要求解边 (u,v),那么就不能出现这条边
所以每条边从出现到结束,出去查询它的时刻,就是它存在的时间
此时,可撤销并查集维护 siz 即可
Pastoral Oddities
https://www.gxyzoj.com/d/gxyznoi/p/P603E
首先,如果满足条件,那么图中所有连通块中的点数都是偶数
其次,对每一个连通块跑最小生成树后,经过删边,一定可以使所有点的度数都是奇数
接下来,可以发现,答案使单调不增的,所以如果一个边被剔除,就不会再加回来
所以可以发现,每个边的存在时间使有范围的,考虑线段树分治
首先排序,可以倒着跑,如果某一条边加入前还有块大小是奇数,这条边从加入到当前位置就是范围
此时直接更新的 vector 即可
点击查看代码
#include<bits/stdc++.h>
#define lid id<<1
#define rid id<<1|1
using namespace std;
int n,m;
struct edge{
int u,v,w,id;
}e[300005];
struct seg_tr{
int l,r;
vector<pair<int,int> > v;
}tr[1200020];
void build(int id,int l,int r)
{
tr[id].l=l,tr[id].r=r;
if(l==r) return;
int mid=(l+r)>>1;
build(lid,l,mid);
build(rid,mid+1,r);
}
void update(int id,int l,int r,int x,int y)
{
if(l>r) return;
if(l==tr[id].l&&r==tr[id].r)
{
tr[id].v.push_back(make_pair(x,y));
return;
}
int mid=(tr[id].l+tr[id].r)>>1;
if(r<=mid) update(lid,l,r,x,y);
else if(l>mid) update(rid,l,r,x,y);
else update(lid,l,mid,x,y),update(rid,mid+1,r,x,y);
}
bool cmp(edge x,edge y)
{
return x.w<y.w;
}
struct node{
int x,y,fa,siz,d;
}st[300005];
int top,siz[300005],f[300005],cnt,pos,ans[300005];
int find(int x)
{
if(f[x]!=x) return find(f[x]);
return x;
}
void merge(int u,int v)
{
int x=find(u),y=find(v);
if(siz[x]<siz[y]) swap(x,y);
if(x==y) return;
st[++top]=(node){x,y,f[y],siz[x],0};
if(siz[x]%2&&siz[y]%2) cnt-=2,st[top].d+=2;
f[y]=x,siz[x]+=siz[y];
}
void split(int x)
{
while(top>x)
{
f[st[top].y]=st[top].fa;
siz[st[top].x]=st[top].siz;
cnt+=st[top].d;
top--;
}
}
void getans(int id,int l,int r)
{
int x=top;
for(int i=0;i<tr[id].v.size();i++)
{
int u=tr[id].v[i].first,v=tr[id].v[i].second;
merge(u,v);
}
if(l==r)
{
while(cnt&&pos<m)
{
if(e[pos+1].id<=l)
{
merge(e[pos+1].u,e[pos+1].v);
update(1,e[pos+1].id,l-1,e[pos+1].u,e[pos+1].v);
}
pos++;
}
// printf("%d %d\n",l,r);
if(cnt==0) ans[l]=e[pos].w;
else ans[l]=-1;
}
else
{
int mid=(l+r)>>1;
getans(rid,mid+1,r);
getans(lid,l,mid);
}
split(x);
}
int main()
{
scanf("%d%d",&n,&m);
build(1,1,m);
for(int i=1;i<=m;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
e[i]=(edge){u,v,w,i};
}
sort(e+1,e+m+1,cmp);
cnt=n;
for(int i=1;i<=n;i++) f[i]=i,siz[i]=1;
// printf("1");
getans(1,1,m);
for(int i=1;i<=m;i++)
{
printf("%d\n",ans[i]);
}
return 0;
}
Extending Set of Points
https://www.gxyzoj.com/d/gxyznoi/p/P1140F
如果把这些二元组抽象成坐标,将所有可加入的点都加入后,一定是若干个方阵
所以可以按横纵坐标考虑,每次由横坐标行纵坐标连边,可以发现,在同一个联通块内的点的数量就是包含横纵坐标数的乘积
因为每个集合存在时间有范围,线段树分治即可
4. 线段树二分
[PA 2015] Siano
https://www.gxyzoj.com/d/gxyznoi/p/P235
顾名思义就是在线段树上二分,先排序,此时在线段树上找到可以砍的第一个,然后区间修改区间求和即可
点击查看代码
#include<cstdio>
#include<algorithm>
#define lid id<<1
#define rid id<<1|1
#define ll long long
using namespace std;
int n,m,a[500005];
struct seg_tree{
int l,r;
ll val,t,sum,tag,lst,l1,l2;
}tr[2000005];
void build(int id,int l,int r)
{
tr[id].l=l,tr[id].r=r;
if(l==r)
{
tr[id].t=tr[id].tag=a[l];
return;
}
int mid=(l+r)>>1;
build(lid,l,mid);
build(rid,mid+1,r);
tr[id].t=tr[rid].t,tr[id].tag=tr[lid].tag+tr[rid].tag;
}
void pushdown(int id)
{
if(tr[id].l2)
{
tr[lid].val=tr[rid].val=tr[lid].l1=tr[rid].l1=tr[id].l1;
tr[lid].lst=tr[rid].lst=tr[lid].l2=tr[rid].l2=tr[id].l2;
tr[lid].sum=1ll*(tr[lid].r-tr[lid].l+1)*tr[id].l1;
tr[rid].sum=1ll*(tr[rid].r-tr[rid].l+1)*tr[id].l1;
tr[id].l2=0;
}
}
int getid(int id,ll d,ll h)
{
if(tr[id].l==tr[id].r)
{
if(tr[id].l==n)
{
if(tr[id].t*(d-tr[id].lst)+tr[id].val<h) return n+1;
}
return tr[id].l;
}
pushdown(id);
ll x=tr[lid].t*(d-tr[lid].lst)+tr[lid].val;
if(x>h) return getid(lid,d,h);
else return getid(rid,d,h);
}
ll query(int id,int l,int r,ll d)
{
if(l>r) return 0;
if(tr[id].l==l&&tr[id].r==r)
{
return tr[id].sum+tr[id].tag*(d-tr[id].lst);
}
pushdown(id);
int mid=(tr[id].l+tr[id].r)>>1;
if(r<=mid) return query(lid,l,r,d);
else if(l>mid) return query(rid,l,r,d);
else return query(lid,l,mid,d)+query(rid,mid+1,r,d);
}
void update(int id,int l,int r,ll d,ll h)
{
if(l>r) return;
if(tr[id].l==l&&tr[id].r==r)
{
tr[id].lst=d,tr[id].sum=(r-l+1)*h,tr[id].val=h;
tr[id].l1=h,tr[id].l2=d;
return;
}
pushdown(id);
int mid=(tr[id].l+tr[id].r)>>1;
if(r<=mid) update(lid,l,r,d,h);
else if(l>mid) update(rid,l,r,d,h);
else update(lid,l,mid,d,h),update(rid,mid+1,r,d,h);
tr[id].lst=tr[rid].lst,tr[id].val=tr[rid].val;
tr[id].sum=tr[lid].tag*(tr[rid].lst-tr[lid].lst)+tr[lid].sum+tr[rid].sum;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
sort(a+1,a+n+1);
build(1,1,n);
while(m--)
{
ll d,h;
scanf("%lld%lld",&d,&h);
int p=getid(1,d,h);
// printf("a%d ",p);
printf("%lld\n",query(1,p,n,d)-h*(n-p+1));
update(1,p,n,d,h);
}
return 0;
}
5. 吉司机线段树
[模板] 线段树3(区间最值操作、区间历史最值)
https://www.gxyzoj.com/d/gxyznoi/p/272
假设不存在 1,5 操作,考虑怎么处理
难点在于不知道要对多少值取 min,所以可以记录每个节点的最大值和次大值,然后不断递归,直到找到第一个满足次大值小于当前值的点更新即可
此时,再记录一下最大值的个数即可,时间复杂度 \(O(log^2 n)\)
然后考虑区间加的操作,因为最大值和其他值的更新次数不一样,所以分开记录即可
接下来看区间历史最值,可以新开一个,记录所对应值的当前段最大增量前缀和,此时,就可以用这个来更新儿子
点击查看代码
#include<cstdio>
#include<algorithm>
#define ll long long
#define lid id<<1
#define rid id<<1|1
using namespace std;
const int N=5e5,inf=2e9;
int n,m,a[N+5];
struct seg_tr{
int l,r;
ll sum,mx,mx1,add1,add2,add3,add4,se,cnt;
}tr[N*4+20];
seg_tr pushup(seg_tr tmp,seg_tr x,seg_tr y)
{
tmp.sum=x.sum+y.sum;
tmp.mx=max(x.mx,y.mx),tmp.mx1=max(x.mx1,y.mx1);
if(x.mx==y.mx)
{
tmp.cnt=x.cnt+y.cnt;
tmp.se=max(x.se,y.se);
}
else if(x.mx<y.mx)
{
tmp.cnt=y.cnt;
tmp.se=max(x.mx,y.se);
}
else
{
tmp.cnt=x.cnt;
tmp.se=max(x.se,y.mx);
}
return tmp;
}
void build(int id,int l,int r)
{
tr[id].l=l,tr[id].r=r;
if(l==r)
{
tr[id].sum=tr[id].mx=tr[id].mx1=a[l];
tr[id].se=-inf,tr[id].cnt=1;
return;
}
int mid=(l+r)>>1;
build(lid,l,mid);
build(rid,mid+1,r);
tr[id]=pushup(tr[id],tr[lid],tr[rid]);
}
void change(int id,ll t1,ll t2,ll t3,ll t4)
{
tr[id].sum+=1ll*t1*tr[id].cnt+1ll*t3*(tr[id].r-tr[id].l+1-tr[id].cnt);
tr[id].mx1=max(tr[id].mx1,tr[id].mx+t2);
tr[id].add2=max(tr[id].add2,tr[id].add1+t2);
tr[id].mx+=t1,tr[id].add1+=t1;
tr[id].add4=max(tr[id].add4,tr[id].add3+t4);
tr[id].add3+=t3;
if(tr[id].se!=-inf) tr[id].se+=t3;
}
void pushdown(int id)
{
ll t1=tr[id].add1,t2=tr[id].add2,t3=tr[id].add3,t4=tr[id].add4,tmp=max(tr[lid].mx,tr[rid].mx);
if(tr[lid].mx==tmp) change(lid,t1,t2,t3,t4);
else change(lid,t3,t4,t3,t4);
if(tmp==tr[rid].mx) change(rid,t1,t2,t3,t4);
else change(rid,t3,t4,t3,t4);
tr[id].add1=tr[id].add2=tr[id].add3=tr[id].add4=0;
}
void add(int id,int l,int r,int x)
{
if(tr[id].l==l&&tr[id].r==r)
{
change(id,x,x,x,x);
return;
}
pushdown(id);
int mid=(tr[id].l+tr[id].r)>>1;
if(r<=mid) add(lid,l,r,x);
else if(l>mid) add(rid,l,r,x);
else add(lid,l,mid,x),add(rid,mid+1,r,x);
tr[id]=pushup(tr[id],tr[lid],tr[rid]);
}
void update(int id,int l,int r,int x)
{
if(x>=tr[id].mx) return;
if(l==tr[id].l&&r==tr[id].r&&x>tr[id].se)
{
change(id,x-tr[id].mx,x-tr[id].mx,0,0);
return;
}
pushdown(id);
int mid=(tr[id].l+tr[id].r)>>1;
if(r<=mid) update(lid,l,r,x);
else if(l>mid) update(rid,l,r,x);
else update(lid,l,mid,x),update(rid,mid+1,r,x);
tr[id]=pushup(tr[id],tr[lid],tr[rid]);
}
ll query1(int id,int l,int r)
{
if(tr[id].l==l&&r==tr[id].r)
{
return tr[id].sum;
}
pushdown(id);
int mid=(tr[id].l+tr[id].r)>>1;
if(r<=mid) return query1(lid,l,r);
else if(l>mid) return query1(rid,l,r);
else return query1(lid,l,mid)+query1(rid,mid+1,r);
}
ll query2(int id,int l,int r)
{
if(l==tr[id].l&&r==tr[id].r)
{
return tr[id].mx;
}
pushdown(id);
int mid=(tr[id].l+tr[id].r)>>1;
if(r<=mid) return query2(lid,l,r);
else if(l>mid) return query2(rid,l,r);
else return max(query2(lid,l,mid),query2(rid,mid+1,r));
}
ll query3(int id,int l,int r)
{
if(l==tr[id].l&&r==tr[id].r)
{
return tr[id].mx1;
}
pushdown(id);
int mid=(tr[id].l+tr[id].r)>>1;
if(r<=mid) return query3(lid,l,r);
else if(l>mid) return query3(rid,l,r);
else return max(query3(lid,l,mid),query3(rid,mid+1,r));
}
int main()
{
// freopen("1.txt","r",stdin);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
build(1,1,n);
while(m--)
{
int opt,l,r,x;
scanf("%d%d%d",&opt,&l,&r);
if(opt==1)
{
scanf("%d",&x);
add(1,l,r,x);
}
if(opt==2)
{
scanf("%d",&x);
update(1,l,r,x);
}
if(opt==3)
{
printf("%lld\n",query1(1,l,r));
}
if(opt==4)
{
printf("%lld\n",query2(1,l,r));
}
if(opt==5)
{
printf("%lld\n",query3(1,l,r));
}
// printf("a %d %d %d\n",opt,l,r);
}
return 0;
}
CPU 监控
https://www.gxyzoj.com/d/gxyznoi/p/P271
可以发现覆盖操作使得区间最值的标记下放很困难,如果不对操作进行区分,下放顺序与其实际顺序不符
但是可以发现,当一段区间被覆盖后,在它被其他操作拆分前,所有操作都可以看作覆盖
所以可以对加和覆盖操作记录最值,下放标记时先放加再放覆盖即可
点击查看代码
#include<cstdio>
#include<algorithm>
#include<iostream>
#define ll long long
#define lid id<<1
#define rid id<<1|1
using namespace std;
const int N=1e5;
const ll inf=2e18;
int n,q;
ll a[N+5];
struct seg_tr{
int l,r;
ll add1,add2,tag1,tag2,mx,mx1,vis;
}tr[N*8+20];
void build(int id,int l,int r)
{
tr[id].l=l,tr[id].r=r;
if(l==r)
{
tr[id].mx=tr[id].mx1=a[l];
return;
}
int mid=(l+r)>>1;
build(lid,l,mid);
build(rid,mid+1,r);
tr[id].mx=max(tr[lid].mx,tr[rid].mx);
tr[id].mx1=max(tr[lid].mx1,tr[rid].mx1);
}
void change(int id,ll t1,ll t2)
{
if(tr[id].vis)
{
tr[id].tag2=max(tr[id].tag2,tr[id].tag1+t2);
tr[id].mx1=max(tr[id].mx1,tr[id].mx+t2);
tr[id].mx+=t1,tr[id].tag1+=t1;
}
else
{
tr[id].add2=max(tr[id].add2,tr[id].add1+t2);
tr[id].mx1=max(tr[id].mx1,tr[id].mx+t2);
tr[id].mx+=t1,tr[id].add1+=t1;
}
}
void cover(int id,ll t1,ll t2)
{
if(tr[id].vis)
{
tr[id].tag2=max(tr[id].tag2,t2);
tr[id].mx1=max(tr[id].mx1,t2);
}
else
{
tr[id].vis=1,tr[id].tag2=t2;
tr[id].mx1=max(tr[id].mx1,t2);
}
tr[id].mx=tr[id].tag1=t1;
}
void pushdown(int id)
{
change(lid,tr[id].add1,tr[id].add2);
change(rid,tr[id].add1,tr[id].add2);
tr[id].add1=tr[id].add2=0;
if(tr[id].vis)
{
cover(lid,tr[id].tag1,tr[id].tag2);
cover(rid,tr[id].tag1,tr[id].tag2);
tr[id].vis=tr[id].tag1=tr[id].tag2=0;
}
}
void add(int id,int l,int r,ll x)
{
if(tr[id].l==l&&tr[id].r==r)
{
change(id,x,x);
return ;
}
pushdown(id);
int mid=(tr[id].l+tr[id].r)>>1;
if(r<=mid) add(lid,l,r,x);
else if(l>mid) add(rid,l,r,x);
else add(lid,l,mid,x),add(rid,mid+1,r,x);
tr[id].mx=max(tr[lid].mx,tr[rid].mx);
tr[id].mx1=max(tr[lid].mx1,tr[rid].mx1);
}
void update(int id,int l,int r,ll x)
{
if(tr[id].l==l&&tr[id].r==r)
{
cover(id,x,x);
return;
}
pushdown(id);
int mid=(tr[id].l+tr[id].r)>>1;
if(r<=mid) update(lid,l,r,x);
else if(l>mid) update(rid,l,r,x);
else update(lid,l,mid,x),update(rid,mid+1,r,x);
tr[id].mx=max(tr[lid].mx,tr[rid].mx);
tr[id].mx1=max(tr[lid].mx1,tr[rid].mx1);
}
ll query1(int id,int l,int r)
{
if(tr[id].l==l&&tr[id].r==r)
{
return tr[id].mx;
}
pushdown(id);
int mid=(tr[id].l+tr[id].r)>>1;
if(r<=mid) return query1(lid,l,r);
else if(l>mid) return query1(rid,l,r);
else return max(query1(lid,l,mid),query1(rid,mid+1,r));
}
ll query2(int id,int l,int r)
{
if(tr[id].l==l&&tr[id].r==r)
{
return tr[id].mx1;
}
pushdown(id);
int mid=(tr[id].l+tr[id].r)>>1;
if(r<=mid) return query2(lid,l,r);
else if(l>mid) return query2(rid,l,r);
else return max(query2(lid,l,mid),query2(rid,mid+1,r));
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
}
build(1,1,n);
scanf("%d",&q);
while(q--)
{
char opt;
int x,y,z;
cin>>opt>>x>>y;
if(opt=='Q')
{
printf("%lld\n",query1(1,x,y));
}
if(opt=='A')
{
printf("%lld\n",query2(1,x,y));
}
if(opt=='P')
{
scanf("%d",&z);
add(1,x,y,z);
}
if(opt=='C')
{
scanf("%d",&z);
update(1,x,y,z);
}
}
return 0;
}
6.李超线段树
就是用于维护某一位置最优线段的线段树
因为在 mid 处的最优线段不一定是整段区间的最优值,所以更新时不能只考虑 mid,分为四种情况
-
均小于,那么不会作为任意一处的最优解,结束
-
均大于,就是整段的最优解,替换掉即可
-
交点在左,那么取 mid 处不优的向左递归
-
交点在右,同理
【模板】李超线段树 / [HEOI2013] Segment
同上
点击查看代码
#include<cstdio>
#include<algorithm>
#define lid id<<1
#define rid id<<1|1
#define ld long double
using namespace std;
const int mod1=39989,mod2=1e9+1,N=1e5+5,M=4e5+5;
int n,cnt;
struct node{
ld k,b;
}a[N];
node get(ld ax,ld ay,ld bx,ld by)
{
if(ax==bx) return (node){0,max(ay,by)};
ld k=(by-ay)/(bx-ax);
ld b=by-bx*k;
return (node){k,b};
}
ld f(int id,int x)
{
return a[id].k*x+a[id].b;
}
struct seg_tr{
int l,r,v;
}tr[M];
void build(int id,int l,int r)
{
tr[id].l=l,tr[id].r=r;
if(l==r) return;
int mid=(l+r)>>1;
build(lid,l,mid);
build(rid,mid+1,r);
}
void update(int id,int l,int r,int x)
{
if(tr[id].l==tr[id].r)
{
if(f(x,tr[id].l)>f(tr[id].v,tr[id].l)) tr[id].v=x;
return;
}
int mid=(tr[id].l+tr[id].r)>>1;
if(l<=tr[id].l&&r>=tr[id].r)
{
int xl=tr[id].l,xr=tr[id].r;
if(f(x,xl)>f(tr[id].v,xl)&&f(x,xr)>f(tr[id].v,xr))
{
tr[id].v=x;
return;
}
if(f(x,xl)<=f(tr[id].v,xl)&&f(x,xr)<=f(tr[id].v,xr)) return;
if(a[x].k>a[tr[id].v].k)
{
if(f(x,mid)>f(tr[id].v,mid)) update(lid,l,r,tr[id].v);
else update(rid,l,r,x);
}
else
{
if(f(x,mid)>f(tr[id].v,mid)) update(rid,l,r,tr[id].v);
else update(lid,l,r,x);
}
if(f(x,mid)>f(tr[id].v,mid)) tr[id].v=x;
return;
}
if(l<=mid) update(lid,l,r,x);
if(r>mid) update(rid,l,r,x);
}
int getmax(int x,int y,int v)
{
if(f(x,v)!=f(y,v))
{
if(f(x,v)>f(y,v)) return x;
return y;
}
return min(x,y);
}
int query(int id,int x)
{
if(tr[id].l==tr[id].r) return tr[id].v;
int mid=(tr[id].l+tr[id].r)>>1;
if(x<=mid) return getmax(tr[id].v,query(lid,x),x);
else return getmax(tr[id].v,query(rid,x),x);
}
int main()
{
scanf("%d",&n);
int ans=0;
build(1,1,mod1);
while(n--)
{
int opt,ax,bx,ay,by;
scanf("%d",&opt);
if(opt==1)
{
scanf("%d%d%d%d",&ax,&ay,&bx,&by);
ax=(ax+ans-1)%mod1+1,bx=(bx+ans-1)%mod1+1;
ay=(ay+ans-1)%mod2+1,by=(by+ans-1)%mod2+1;
if(ax>bx) swap(ax,bx),swap(ay,by);
a[++cnt]=get(ax,ay,bx,by);
// printf("%.2lf %.2lf\n",a[cnt].k,a[cnt].b);
update(1,ax,bx,cnt);
}
else
{
scanf("%d",&ax);
ax=(ax+ans-1)%mod1+1;
ans=query(1,ax);
printf("%d\n",ans);
}
}
return 0;
}
[SDOI2016] 游戏
先树剖,此时就变成了若干个一次函数,因为不想动态开点,所以可以单开一个数组,记录每个点对应的实际的 x 值
因为进行了树剖,所以每个点的 x 就是它到 top 的距离,直接预处理并相减即可
剩下求区间最小值的问题,可以发现,如果一个线段完全包含一个区间,那么最小值一定在端点处
所以每次更新,如果完全包含,就求出两端的函数值取 min,这一部分可以 pushup
但是李超线段树本身为减少时间,要标记永久化,所以询问的时候经过的点也要算贡献
点击查看代码
#include<cstdio>
#include<algorithm>
#define lid id<<1
#define rid id<<1|1
#define ll long long
using namespace std;
const int N=1e5+5;
const ll inf=123456789123456789;
int n,m,head[N],edgenum,f[N],son[N],siz[N],dep[N];
int dfn[N],top[N],idx;
ll dis[N],d[N];
struct node{
ll k,b;
};
struct edge{
int to,nxt,val;
}e[N*2];
void add_edge(int u,int v,int w)
{
e[++edgenum].nxt=head[u];
e[edgenum].to=v;
e[edgenum].val=w;
head[u]=edgenum;
}
void dfs(int u,int fa)
{
dep[u]=dep[fa]+1,siz[u]=1,f[u]=fa;
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(v==fa) continue;
dis[v]=dis[u]+e[i].val;
dfs(v,u);
siz[u]+=siz[v];
if(siz[v]>siz[son[u]]) son[u]=v;
}
}
void dfs1(int u,int tp)
{
top[u]=tp,dfn[u]=++idx,d[dfn[u]]=dis[u]-dis[tp];
if(son[u])
{
dfs1(son[u],tp);
}
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(v==son[u]||v==f[u]) continue;
dfs1(v,v);
}
}
struct seg_tr{
int l,r;
node v;
ll mn;
}tr[N*8];
ll F(node g,ll x)
{
return g.k*x+g.b;
}
void build(int id,int l,int r)
{
tr[id].l=l,tr[id].r=r;
tr[id].v=(node){0,inf},tr[id].mn=inf;
if(l==r) return ;
int mid=(l+r)>>1;
build(lid,l,mid);
build(rid,mid+1,r);
}
void update(int id,int l,int r,node x)
{
if(tr[id].l==tr[id].r)
{
if(F(x,d[tr[id].l])<F(tr[id].v,d[tr[id].l])) tr[id].v=x,tr[id].mn=F(x,d[tr[id].l]);
return;
}
int mid=(tr[id].l+tr[id].r)>>1;
if(l<=tr[id].l&&r>=tr[id].r)
{
ll xl=d[tr[id].l],xr=d[tr[id].r],xm=d[mid];
if(F(x,xl)<F(tr[id].v,xl)&&F(x,xr)<F(tr[id].v,xr))
{
tr[id].v=x;
tr[id].mn=min(tr[id].mn,min(F(x,xl),F(x,xr)));
return;
}
if(F(x,xl)>=F(tr[id].v,xl)&&F(x,xr)>=F(tr[id].v,xr)) return;
if(x.k>tr[id].v.k)
{
if(F(x,xm)<F(tr[id].v,xm)) update(rid,l,r,tr[id].v);
else update(lid,l,r,x);
}
else
{
if(F(x,xm)<F(tr[id].v,xm)) update(lid,l,r,tr[id].v);
else update(rid,l,r,x);
}
if(F(x,xm)<F(tr[id].v,xm)) tr[id].v=x;
tr[id].mn=min(tr[id].mn,min(F(x,xl),F(x,xr)));
}
else
{
if(l<=mid) update(lid,l,r,x);
if(r>mid) update(rid,l,r,x);
}
tr[id].mn=min(tr[id].mn,min(tr[lid].mn,tr[rid].mn));
// printf("%d %d %lld %lld %d\n",tr[id].l,tr[id].r,x.k,x.b,tr[id].mn);
}
ll query(int id,int l,int r)
{
if(tr[id].l==l&&tr[id].r==r)
{
return tr[id].mn;
}
int mid=(tr[id].l+tr[id].r)>>1;
ll tmp=min(F(tr[id].v,d[l]),F(tr[id].v,d[r]));
if(r<=mid) return min(query(lid,l,r),tmp);
else if(l>mid) return min(query(rid,l,r),tmp);
else return min(tmp,min(query(lid,l,mid),query(rid,mid+1,r)));
}
int lca(int u,int v)
{
while(top[u]!=top[v])
{
if(dep[top[u]]<dep[top[v]]) swap(u,v);
u=f[top[u]];
}
if(dep[u]>dep[v]) return v;
return u;
}
void change(int u,int lc,ll a,ll b)
{
while(top[u]!=top[lc])
{
update(1,dfn[top[u]],dfn[u],(node){-a,b+(dis[u]-dis[top[u]])*a});
b+=(dis[u]-dis[f[top[u]]])*a;
u=f[top[u]];
}
update(1,dfn[lc],dfn[u],(node){-a,b+(dis[u]-dis[top[u]])*a});
}
ll getans(int u,int v)
{
ll ans=inf;
while(top[u]!=top[v])
{
if(dep[top[u]]<dep[top[v]]) swap(u,v);
ans=min(ans,query(1,dfn[top[u]],dfn[u]));
u=f[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
ans=min(ans,query(1,dfn[u],dfn[v]));
return ans;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add_edge(u,v,w);
add_edge(v,u,w);
}
dfs(1,0);
dfs1(1,1);
// for(int i=1;i<=n;i++) printf("%lld ",dfn[i]);
build(1,1,n);
while(m--)
{
int opt,s,t;
ll a,b;
scanf("%d%d%d",&opt,&s,&t);
if(opt==1)
{
scanf("%lld%lld",&a,&b);
int lc=lca(s,t);
ll len=dis[s]+dis[t]-dis[lc]*2;
change(s,lc,a,b);
change(t,lc,-a,len*a+b);
// printf("%lld\n",len);
}
else
{
printf("%lld\n",getans(s,t));
}
}
return 0;
}

浙公网安备 33010602011771号