线段树合并小结

一种新的线段树 \(pushup\) 方法:

friend node operator + (const node &xx,const node &yy)
{
	node z;
	z.cnt=xx.cnt+yy.cnt;
	z.sum=xx.sum+yy.sum;
	return z;
}

然后改的时候就直接使用

tr[now]=tr[lid]+tr[rid];

权值线段树

就是把线段树变成桶。

用线段树维护桶。

代码:

模板:P1138 第 k 小整数

#include<bits/stdc++.h>
using namespace std;
int n,k;
struct segmentTree{
	struct node{
		int sum;
	}tr[40000<<2];
	#define lid now<<1
	#define rid now<<1|1
	void update(int now,int l,int r,int x,int y,int k)
	{
		if(x<=l&&r<=y)
		{
			tr[now].sum=k;return ;
		}
		int mid=(l+r)>>1;
		if(x<=mid) update(lid,l,mid,x,y,k);
		if(y>mid) update(rid,mid+1,r,x,y,k);
		tr[now].sum=tr[lid].sum+tr[rid].sum; 
	}
	int query(int now,int l,int r,int k)
	{
		if(l==r) return l;
		int mid=(l+r)>>1;
		if(tr[lid].sum>=k)  query(lid,l,mid,k);
		else  query(rid,mid+1,r,k-tr[lid].sum);
	}
}st;
int cnt;
int main()
{
	cin>>n>>k;
	for(int i=1;i<=n;i++) 
	{
		int t;cin>>t;
		st.update(1,1,30000,t,t,1);
	//	cout<<"upd\n";
	}
	if(k<=0||k>=st.tr[1].sum)
	{
		cout<<"NO RESULT";return 0;
	}
	cout<<st.query(1,1,30000,k);
}

作用

  1. 查询第 \(k\) 大的数。
int kth(int now,int l,int r,int k)
{
	if(l==r) return l;
	int mid=(l+r)>>1;
	if(tr[lid].sum>=k) return kth(lid,l,mid,k);
	else return kth(rid,mid+1,r,k-tr[lid].sum);
}
  1. 算逆序对(就是模板粘上去即可)

动态开点树

可以是线段树,也可以是权值线段树。

代码:

模板: P3369 【模板】普通平衡树

  • 要注意的一点是,\(update\) 的时候的 \(now\) 要写成 int &now 来更改值;

  • 每个操作 \(update\) 或者 \(query\) 的范围可以是负数、传进去的参数也可以是负数。

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n;
int root=1,cnt=1;
int mx=2e7+1;
struct sgt{
	struct node{
		int sum,ls,rs;
	}tr[2000000<<1];
	#define lid tr[now].ls
	#define rid tr[now].rs
	void update(int &now,int l,int r,int x,int y,int k)
	{
		if(!now) now=++cnt;
		if(x<=l&&r<=y)
		{
			tr[now].sum+=k;
			return ;
		 } 
		int mid=(l+r)>>1;
		if(x<=mid) update(lid,l,mid,x,y,k);
		if(y>mid) update(rid,mid+1,r,x,y,k);
		tr[now].sum=tr[lid].sum+tr[rid].sum;
	}
	int query(int now,int l,int r,int x,int y)
	{
		if(x<=l&&r<=y) return tr[now].sum;
		int mid=(l+r)>>1,res=0;
		if(x<=mid) res+=query(lid,l,mid,x,y);
		if(y>mid) res+=query(rid,mid+1,r,x,y);
		return res; 
	}
	int kth(int now,int l,int r,int k)
	{
		if(l==r) return l;
		int mid=(l+r)>>1;
		if(tr[lid].sum>=k) return kth(lid,l,mid,k);
		else return kth(rid,mid+1,r,k-tr[lid].sum);
	}
}st;
map<int,int>mp;
signed main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		int op,t;
		scanf("%lld%lld",&op,&t);
		if(op==1)
		{
			st.update(root,-mx,mx,t,t,1);
			mp[t]++;
		}
		else if(op==2)
		{
			st.update(root,-mx,mx,t,t,-1);
			mp[t]--;
			if(!mp[t]) mp.erase(t);
		}
		else if(op==3)
		{
			cout<<st.query(root,-mx,mx,-mx,t-1)+1<<endl;
		}
		else if(op==4)
		{
			int res=st.kth(root,-mx,mx,t);
			cout<<res<<endl;
		}
		else if(op==5)
		{
			cout<<(--mp.lower_bound(t))->first<<endl;
		}
		else if(op==6)
		{
			cout<<mp.upper_bound(t)->first<<endl;
		}
	}
}

作用

缩小使用的空间。

例题

有的题目多解,可以直接使用权值线段树也可以使用线段树合并。

例题1 P3369 【模板】普通平衡树

看似是平衡树模板,实则是动态开点权值线段树模板。

只不过查找前驱后继的操作我拿 \(map\) 水过了。。

代码在上面。

例题2 #P1644. bzoj4636: 韶身的数列

这是拿动态开点权值线段树做的。

但是里面有一个操作很迷,就是修改比 \(k\) 小的数为 \(k\)

void ckmx(int &x,int y)
{
	x=(y>x?y:x);
}
void update(int &now,int l,int r,int x,int y,int k)
{
	if(!now) now=++cnt;
	if(x<=l&&r<=y)
	{
		ckmx(tr[now].mx,k);//这里
		return ;
	}
	int mid=(l+r)>>1;
	if(x<=mid) update(lid,l,mid,x,y,k);
	if(y>mid) update(rid,mid+1,r,x,y,k);
}

还有就是查询的神秘操作:

void query(int now,int l,int r,int pre)
{
	if(!now&&!pre) return ;
	int mid=(l+r)>>1;
	int nnow=max(pre,tr[now].mx);//这里
	if(l==r) ans+=nnow;
	else query(lid,l,mid,nnow),query(rid,mid+1,r,nnow);
}

例题3 P3605 [USACO17JAN] Promotion Counting P

这个题很好,我拿动态开点权值线段树做的。

题意就是求树上逆序对,整体方法是 \(dfs\)

对于一个答案 \(ans[u]\)

\(ans[u]=\) 加上子树中比它大的个数 - 原本线段树中比它大的。

这样我们每 \(dfs\) 到一个节点 \(u\),先在 \(ans[u]\) 中减去比 \(u\) 大的个数,然后 \(dfs\) 子节点,之后再给 \(ans[u]\) 加上线段树中比它大的。

最后,把当前节点的权值 \(update\) 进去。

void dfs(int u,int fa)
{
	ans[u]-=st.query(root,1,mx,ww[u]+1,mx);
	for(int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if(v==fa) continue;
		dfs(v,u);
	}
	ans[u]+=st.query(root,1,mx,ww[u]+1,mx);
	st.update(root,1,mx,ww[u],ww[u],1);
}

然后有个细节:我们有可能 \(query\) 的节点是空的,所以在 \(query\) 函数中需要加上一行 if(!now) return 0;

具体来说是这样的:

int query(int now,int l,int r,int x,int y)
{
	if(!now) return 0;
	if(x<=l&&r<=y) return tr[now].sum;
	int mid=(l+r)>>1,res=0;
	if(x<=mid) res+=query(lid,l,mid,x,y);
	if(y>mid) res+=query(rid,mid+1,r,x,y);
	return res; 
}

例题4 P4556 [Vani有约会] 雨天的尾巴 /【模板】线段树合并

线段树合并 + 树链剖分

每个节点都是一个(动态开点)权值线段树,

然后树上差分:

  • \(u\) 到根节点加上 \(1\);
  • \(v\) 到根节点加上 \(1\)
  • \(lca\) 到根节点减去 \(1\)
  • \(fa[lca]\) 到根节点减去 \(1\)

然后再一遍 dfs 合并线段树,至此结束全部的神秘操作。

线段树结构体数组要开 30 倍

待完全理解。。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N=3e5+1;
struct node{
	int to,next;
}edge[N<<1];
int head[N],ccnt;
void add(int u,int v)
{
	edge[++ccnt].next=head[u];
	edge[ccnt].to=v;
	head[u]=ccnt;
}
int ans[N];
int siz[N],son[N],dep[N],f[N];
void dfs1(int u,int fa)
{
	siz[u]=1,dep[u]=dep[fa]+1;
	f[u]=fa;
	for(int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if(v==fa) continue;
		dfs1(v,u);
		siz[u]+=siz[v];
		if(siz[son[u]]<siz[v]) son[u]=v;
	}
}
int top[N],id[N],tim;
void dfs2(int u,int t)
{
	id[u]=++tim;top[u]=t;
	if(!son[u]) return;
	dfs2(son[u],t);
	for(int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if(v==f[u]||v==son[u]) continue;
		dfs2(v,v);
	}
}
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]) swap(u,v);
	return u;
}
int root=1,cnt=1;
struct SegTree{
	struct nodee{
		int ls,rs,sum,tre;
	}tr[N*30];
	#define lid tr[now].ls
	#define rid tr[now].rs
	void pushup(int now)
	{
		if(tr[lid].sum>=tr[rid].sum)
			tr[now].sum=tr[lid].sum,tr[now].tre=tr[lid].tre;
		else
			tr[now].sum=tr[rid].sum,tr[now].tre=tr[rid].tre;
	}
	void change(int &now,int l,int r,int pos,int val)
	{
		if(!now) now=++cnt;
		if(l==r)
		{
			tr[now].sum+=val;
			tr[now].tre=pos;return;
		}
		int mid=(l+r)>>1;
		if(pos<mid) change(lid,l,mid,pos,val);
		else change(rid,mid+1,r,pos,val);
		pushup(now);
	}
	int merge(int a,int b,int l,int r)
	{
		if(a==0||b==0) return a+b;
		if(l==r)
		{
			tr[a].sum+=tr[b].sum;return a;
		}
		int mid=(l+r)>>1;
		tr[a].ls=merge(tr[a].ls,tr[b].ls,l,mid);
		tr[a].rs=merge(tr[a].rs,tr[b].rs,mid+1,r);
		pushup(a);
		return a;
	 } 
	 int rot[N];
	 void calc(int u,int fa)
	{
		for(int i=head[u];i;i=edge[i].next)
		{
			int v=edge[i].to;
			if(v==fa) continue;
			calc(v,u);
			rot[u]=merge(rot[u],rot[v],1,100000);
		 } 
		ans[u]=tr[rot[u]].tre;
		if(tr[rot[u]].sum==0) ans[u]=0;
	 } 
}st;
int main()
{
	cin>>n>>m;
	for(int i=1;i<n;i++)
	{
		int u,v;cin>>u>>v;
		add(u,v),add(v,u);
	}
	dfs1(1,0),dfs2(1,1);
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		cin>>u>>v>>w;
		int lc=lca(u,v);
		st.change(st.rot[u],1,100000,w,1);
		st.change(st.rot[v],1,100000,w,1);
		st.change(st.rot[lc],1,100000,w,-1);
		st.change(st.rot[f[lc]],1,100000,w,-1);
	}
	st.calc(1,0);
	for(int i=1;i<=n;i++)
		cout<<ans[i]<<endl;
}

例题5 P3224 [HNOI2012] 永无乡

芝士模板。

但是又有玄学操作!

用并查集维护联通块。用动态开点权值线段树和线段树合并维护区间第 \(k\) 大。

因为查询第 \(k\) 大的代码参数写反了,一直跑不出来。。。。。

#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int f[N];
int find(int x)
{
	if(x!=f[x])
		f[x]=find(f[x]);
	return f[x];
}
int ccnt=1;int num[N];
struct SegTrMerge{
	struct node{
		int ls,rs,sum,id;
	}tr[N*30];
	#define lid tr[now].ls
	#define rid tr[now].rs
	void pushup(int now){tr[now].sum=tr[lid].sum+tr[rid].sum;}
	void change(int &now,int l,int r,int x)
	{
		if(!now) now=++ccnt;
		tr[now].sum++;
		if(l==r)
			return ;
		int mid=(l+r)>>1;
		if(x<=mid) change(lid,l,mid,x);
		else change(rid,mid+1,r,x);
	}
	int query(int now,int l,int r,int k)
	{
		if(tr[now].sum<k||!now) return 0;
		if(l==r) return num[l];
		int mid=(l+r)>>1;
		if(k<=tr[lid].sum) return query(lid,l,mid,k);
		else return query(rid,mid+1,r,k-tr[lid].sum);
	}
	void merge(int &a,int b)
	{
		if(!a){a=b;return ;}
		if(!b) return ;
		tr[a].sum+=tr[b].sum;
		merge(tr[a].ls,tr[b].ls);
		merge(tr[a].rs,tr[b].rs);
	}
}st;
int root[N];
int main()
{
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++) 
	{
		int x;
		cin>>x;f[i]=i;num[x]=i;
		st.change(root[i],1,n,x);
	}
	for(int i=1;i<=m;i++)
	{
		int u,v;
		cin>>u>>v;
		u=find(u),v=find(v);
		f[v]=u;
		st.merge(root[u],root[v]);
	}
	int q;cin>>q;
	while(q--)
	{
		string c;cin>>c;
		int u,v;
		cin>>u>>v;
		if(c[0]=='Q')
		{
			u=find(u);
			if(st.tr[root[u]].sum<v)
			{
				cout<<-1<<endl;
				continue;
			}
			cout<<st.query(root[u],1,n,v)<<endl;
		}
		else
		{
			u=find(u),v=find(v);
			if(u==v) continue;
			f[v]=u;
			st.merge(root[u],root[v]);
		}
	}
}

例题6 P4219 [BJOI2014] 大融合

把它放到例题里面纯粹是因为在波波的训练里头。

我是参考洛谷的一篇树剖解子做的。

用树剖+并查集来想就会非常简单。

首先把操作离线,建一个操作里叙述的森林,

然后建一个虚拟根节点,把森林连成一棵树。(也可以直接使用 \(lct\)

跑树剖代码,把树状数组初始化一下,清空并查集。

然后对于一个操作:

  • 操作 \(A\)
    \(find(x)\)\(x\) 的路径上每个点都加上 \(query(y)\)
  • 操作 \(Q\)
    如果 \(x\)\(y\) 的父节点,那么答案就是 \((query(x)-query(y))*(query(y))\)

query(x) 表示当前 \(x\) 的子树大小。

#include<bits/stdc++.h>
using namespace std;
int n,q;
const int N=1e5+2;
int f[N];
int find(int x)
{
	if(x!=f[x]) f[x]=find(f[x]);
	return f[x];
}
struct operation{
	int tp,x,y;
}op[N];
struct node{
	int to,next;
}edge[N<<1];
int head[N],cnt;
void add(int u,int v)
{
	edge[++cnt].to=v;
	edge[cnt].next=head[u];
	head[u]=cnt;
}
void read()
{
	cin>>n>>q;
	for(int i=1;i<=n;i++) f[i]=i;
	for(int i=1;i<=q;i++)
	{
		getchar();
		char c=getchar();
		if(c=='A')	op[i].tp=1;
		else op[i].tp=0;
		cin>>op[i].x>>op[i].y;
		if(op[i].tp)
		{
			add(op[i].x,op[i].y),add(op[i].y,op[i].x);
			f[find(op[i].y)]=find(op[i].x);
		}
	}
}
int tim,top[N],id[N],siz[N],son[N],dep[N],father[N];
void dfs1(int u)
{
	siz[u]=1;
	for(int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if(v==father[u]) continue;
		father[v]=u;
		dep[v]=dep[u]+1;
		dfs1(v);
		siz[u]+=siz[v];
		if(siz[son[u]]<siz[v]) son[u]=v;
	}
}
void dfs2(int u,int t)
{
	top[u]=t,id[u]=++tim;
	if(!son[u]) return ;
	dfs2(son[u],t);
	for(int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if(v==father[u]||v==son[u]) continue;
		dfs2(v,v);
	}
}
int lowbit(int x){return x&-x;}
int c[N];
void update(int x,int k){
	for(;x<=n;x+=lowbit(x))c[x]+=k;
}
int query(int x)
{
	int res=0;
	for(;x>0;x-=lowbit(x))
	res+=c[x];
	return res;
}
int addpath(int u,int v,int k)
{
	while(top[u]!=top[v])
	{
		update(id[top[u]],k),update(id[u]+1,-k);
		u=father[top[u]];
	}
	update(id[v],k),update(id[u]+1,-k);
}
int main()
{
	read();
	for(int i=1;i<=n;i++)
	{
		if(find(i)==i)	add(i,n+1),add(n+1,i);//连森林成一棵树
	}
	n++;//玄学
	dfs1(n),dfs2(n,n+1);
	update(1,1);
	for(int i=1;i<=n;i++) f[i]=i;//cout<<c[i]<<" ";
	for(int i=1;i<=q;i++)
	{
		int x=op[i].x,y=op[i].y;
		if(f[x]==y) swap(x,y);
		if(op[i].tp==1)
		{
			addpath(x,find(x),query(id[y]));//cout<<"add\n";
			f[find(y)]=find(x);
		}
		else
		{
			long long ans=0;
			int s=query(id[find(x)]),s1=query(id[y]);
			ans=(s-s1)*s1;
			cout<<ans<<endl;
		}
	}
}
posted @ 2024-02-28 18:09  ccjjxx  阅读(17)  评论(2编辑  收藏  举报