线段树综合学习笔记

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,分为四种情况

  1. 均小于,那么不会作为任意一处的最优解,结束

  2. 均大于,就是整段的最优解,替换掉即可

  3. 交点在左,那么取 mid 处不优的向左递归

  4. 交点在右,同理

【模板】李超线段树 / [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;
}
posted @ 2025-03-26 21:01  wangsiqi2010916  阅读(25)  评论(0)    收藏  举报