线段树合并总结

**~~

线段树合并总结

**~~
**本来是打算补套数据结构的SAM的结果看到一个题要用这个东西。什么玩意儿我怎么没见过 就先补一下

大概内容就是对每个点开一颗权值线段树(根据情况可能会维护除了siz外的其他信息),需要的时候合并两个节点的树维护的信息。具体用途还是看题吧。
单次操作的复杂度o(logn)
模板

struct node{
	int ls,rs,siz;//维护的内容与pushup等函数要按具体情况修改
}tr[log(MAX_N)*add调用次数];
int tot=0,rt[MAX_N];
inline void pushup(int k)
{
	tr[k].siz=tr[tr[k].ls].siz+tr[tr[k].rs].siz; 
}
void add(int &k,int l,int r,int val)
{
	if(!k) k=++tot;
	if(l==r){tr[k].siz+=val;return ;}
	int mid=(l+r)>>1;
	if(pos<=mid) add(tr[k].ls,l,mid,pos,val);
	else add(tr[k].rs,mid+1,r,pos,val);
	pushup(k);
}
void merge(int l,int r,int &p,int q)
{
	if(!p||!q){p=(!p)?q:p;return ;}
	if(l==r){tr[p].siz+=tr[q].siz; return;}
	int mid=(l+r)>>1;
	merge(l,mid,tr[p].ls,tr[q].ls);
	merge(mid+1,r,tr[p].rs,tr[q].rs);
	pushup(p);
}

例题:
1.P3521 给一颗有N个叶子的树,可以交换每个节点的左右子树,问前序遍历情况下逆序对最少几个
对于一棵树,逆序对存在3种情况-都在左树,都在右树,左右交叉,交换左右子树只影响第三种
故对第三种取最小值即可(不交换的情况左子树的右子树大小*右子树的左子树大小,交换则相反),对于左右子树同在左/右侧的情况,会在向下合并区间时更新。边合并边更新。

const int MAX_N=2e5+5;
struct node{
	int ls,rs,siz;
}tr[MAX_N*22];//每个叶节点实际上建的是一条链,用到logn个节点,故共开nlogn个节点
int n,tot=0;
ll ans=0,u,v;
int update(int l,int r,int val)//对每个叶节点建权值线段树
{
	int k=++tot;
	tr[k].siz++;
	if(l==r) return k;
	int mid=(l+r)>>1;
	if(val<=mid) tr[k].ls=update(l,mid,val);
	else tr[k].rs=update(mid+1,r,val);
	return k;
}
int merge(int l,int r,int p,int q)
{
	if(!p||!q) return (!p)?q:p;
	if(l==r){
		tr[p].siz+=tr[q].siz; return p;
	}
	u+=1ll*tr[tr[p].rs].siz*tr[tr[q].ls].siz//不交换
	v+=1ll*tr[tr[p].ls].siz*tr[tr[q].rs].siz;//交换
	int mid=(l+r)>>1;
	tr[p].ls=merge(l,mid,tr[p].ls,tr[q].ls);//对于同侧的会在合并时计算
	tr[p].rs=merge(mid+1,r,tr[p].rs,tr[q].rs);
	tr[p].siz=tr[tr[p].ls].siz+tr[tr[p].rs].siz;
	return p;
}
int dfs()
{
	int pos,val; si(val);
	if(!val){
		int ls=dfs(),rs=dfs();
		u=v=0;
		pos=merge(1,n,ls,rs);
		ans+=min(u,v);
	}
	else pos=update(1,n,val);//叶子节点,建树
	return pos;
}
int main()
{
	si(n);
	dfs();
	printf("%lld\n",ans);
}

2.P3224 n个点,每个点重要度不同(名次1-n与序号不同),开始的时候一些点间连了边。有路径的两点称为连通。给出两种操作B x y x与y连边 Q x k 询问x所在连通块重要度排名第k小是哪座岛
并查集+线段树合并 注意并查集合并y并给x的时候也是把y树合到x树上以及每次操作都是对根进行的 对于第K小的查询为权值线段树的基本应用,此处略过

const int MAX_N=1e5+5;
struct Union_Find{
	int fa[MAX_N];
	void init(int n){repi(i,0,n)fa[i]=i;} 
	int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
}uf;
int n,m;
int a[MAX_N],rt[MAX_N],id[MAX_N]; 
struct node{
	int ls,rs,siz;
}tr[MAX_N*22];
int tot=0;
int add(int l,int r,int val)
{
	int k=++tot; tr[k].siz++;
	if(l==r) return k;
	int mid=(l+r)>>1;
	if(val<=mid) tr[k].ls=add(l,mid,val);
	else tr[k].rs=add(mid+1,r,val);
	return k;
}
int merge(int l,int r,int p,int q)
{
	if(!p||!q) return (!p)?q:p;
	if(l==r){
		tr[p].siz+=tr[q].siz; return p;
	}
	int mid=(l+r)>>1;
	tr[p].ls=merge(l,mid,tr[p].ls,tr[q].ls);
	tr[p].rs=merge(mid+1,r,tr[p].rs,tr[q].rs);
	tr[p].siz=tr[tr[p].ls].siz+tr[tr[p].rs].siz;
	return p;
}
int query(int k,int l,int r,int kth)
{
	if(l==r) return l;
	int mid=(l+r)>>1;
	if(kth<=tr[tr[k].ls].siz) return query(tr[k].ls,l,mid,kth);
	else return query(tr[k].rs,mid+1,r,kth-tr[tr[k].ls].siz);
}
int main()
{
	si(n),si(m);
	uf.init(n);
	repi(i,1,n) si(a[i]),rt[i]=add(1,n,a[i]),id[a[i]]=i;
	repi(i,1,m){
		int x,y; si(x),si(y); x=uf.find(x),y=uf.find(y);
		if(x!=y) uf.fa[y]=x,rt[x]=merge(1,n,rt[x],rt[y]);
	}
	int q; si(q);
	while(q--){
		char s[10]; ss(s+1);
		int x,y; si(x),si(y);
		if(s[1]=='B'){	
			x=uf.find(x),y=uf.find(y);
			if(x!=y) uf.fa[y]=x,rt[x]=merge(1,n,rt[x],rt[y]);
		}
		else{
			x=uf.find(x);
			int ans;
			if(tr[rt[x]].siz<y) ans=-1;
		 	else ans=id[query(rt[x],1,n,y)];
			printf("%d\n",ans);
		}
	}
	return 0;
}

3.P3605 n个点,每个点一个权值,问以每个点为根的子树中有几个点权值大于根权值
对权值先进行离散化处理,由叶子向上进行线段树合并即可。

const int MAX_N=1e5+5;
struct Edge{
	int to,nxt;
}e[MAX_N<<1];
int head[MAX_N],tote;
void add_edge(int u,int v)
{
	e[++tote].to=v,e[tote].nxt=head[u];
	head[u]=tote;
}
void init(int n)
{
	repi(i,0,n) head[i]=0;
	tote=0;
}
int n,a[MAX_N];
int rt[MAX_N]; 
vector<int> h; 
int cnt;
struct node{
	int ls,rs,siz;
}tr[MAX_N*22];
int tot=0;
int add(int l,int r,int val)
{
	int k=++tot; tr[k].siz++;
	if(l==r) return k;
	int mid=(l+r)>>1;
	if(val<=mid) tr[k].ls=add(l,mid,val);
	else tr[k].rs=add(mid+1,r,val);
	return k;
}
int merge(int l,int r,int p,int q)
{
	if(!p||!q) return (!p)?q:p;
	if(l==r){
		tr[p].siz+=tr[q].siz; return p;
	}
	int mid=(l+r)>>1;
	tr[p].ls=merge(l,mid,tr[p].ls,tr[q].ls);
	tr[p].rs=merge(mid+1,r,tr[p].rs,tr[q].rs);
	tr[p].siz=tr[tr[p].ls].siz+tr[tr[p].rs].siz;
	return p;
}
int query(int k,int l,int r,int val)
{
	if(l==r) return tr[k].siz;
	int mid=(l+r)>>1;
	if(val<=mid) return query(tr[k].ls,l,mid,val)+tr[tr[k].rs].siz;
	else return query(tr[k].rs,mid+1,r,val);
}
int ans[MAX_N];
void dfs(int s,int fa){
	reps(s)if(e[i].to!=fa){
		dfs(e[i].to,s);
		rt[s]=merge(1,cnt,rt[s],rt[e[i].to]);
	}
	ans[s]=query(rt[s],1,cnt,a[s]+1);
}
int main()
{
	si(n); h.pb(-1);
	init(n);
	repi(i,1,n) si(a[i]),h.pb(a[i]);
	sort(all(h)),h.erase(unique(all(h)),h.end());
	cnt=h.size();
	repi(i,1,n) a[i]=lower_bound(all(h),a[i])-h.begin(),rt[i]=add(1,cnt,a[i]);
	repi(i,2,n){
		int fa; si(fa);
		add_edge(i,fa),add_edge(fa,i);
	}
	dfs(1,1);
	repi(i,1,n) printf("%d\n",ans[i]);
	return 0;
}

4.P4556 n个点,树形结构,每次x到y的路径每个点发一袋z种粮食,问最后每个点哪种粮食最多(相等的取编号小的)
树上差分处理每次操作 x y节点+1 lca(x,y)和fa[lca(x,y)]-1,由下向上合并即可

const int MAX_N=1e5+5;
struct Edge{
	int to,nxt;
}e[MAX_N<<1];
int head[MAX_N],tote;
void add_edge(int u,int v)
{
	e[++tote].to=v,e[tote].nxt=head[u];
	head[u]=tote;
}

int fa[MAX_N],dep[MAX_N],siz[MAX_N],top[MAX_N],son[MAX_N];
int idx,dfn[MAX_N],rdfn[MAX_N];
void dfs_1(int u,int pre)
{
	dep[u]=dep[pre]+1,fa[u]=pre,siz[u]=1;
	reps(u)if(e[i].to!=pre){
		int v=e[i].to;
		dfs_1(v,u);
		siz[u]+=siz[v];
		if(son[u]==-1||siz[son[u]]<siz[v])	son[u]=v;
	}
}
void dfs_2(int u,int tp)
{
	top[u]=tp,dfn[u]=++idx,rdfn[dfn[u]]=u;
	if(son[u]==-1)	return;
	dfs_2(son[u],tp);
	reps(u){
		int v=e[i].to;
		if(v!=fa[u]&&v!=son[u])	dfs_2(v,v);
	}
}
int LCA(int x,int y)
{
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]])	swap(x,y);
		x=fa[top[x]];
	}
	return dep[x]<dep[y]?x:y;
}
int n,m;
int x[MAX_N],y[MAX_N],z[MAX_N],mx=0;
struct node{
	int ls,rs,siz,mn;
}tr[MAX_N*60];
int tot=0,rt[MAX_N];
inline void pushup(int k)
{
	int pos=((tr[tr[k].ls].siz>=tr[tr[k].rs].siz)?tr[k].ls:tr[k].rs);
	tr[k].siz=tr[pos].siz,tr[k].mn=tr[pos].mn;
}
void add(int &k,int l,int r,int pos,int val)
{
	if(!k) k=++tot;
	if(l==r){
		tr[k].siz+=val,tr[k].mn=l; return ;
	}
	int mid=(l+r)>>1;
	if(pos<=mid) add(tr[k].ls,l,mid,pos,val);
	else add(tr[k].rs,mid+1,r,pos,val);
	pushup(k);
}
void merge(int l,int r,int &p,int q)
{
	if(!p||!q){p=(!p)?q:p;return ;}
	if(l==r){
		tr[p].siz+=tr[q].siz,tr[p].mn=l; return;
	}
	int mid=(l+r)>>1;
	merge(l,mid,tr[p].ls,tr[q].ls);
	merge(mid+1,r,tr[p].rs,tr[q].rs);
	pushup(p);
}
int ans[MAX_N];
void dfs_3(int s,int pre)
{
	reps(s)if(e[i].to!=pre) dfs_3(e[i].to,s),merge(1,mx,rt[s],rt[e[i].to]);
	if(tr[rt[s]].siz) ans[s]=tr[rt[s]].mn;
}
void init(int n)
{
	repi(i,0,n) head[i]=0,son[i]=-1;
	tote=idx=dep[0]=0;
}
int main()
{
	si(n),si(m); init(n);
	repi(i,1,n-1){
		int u,v; si(u),si(v);
		add_edge(u,v),add_edge(v,u);
	}
	dfs_1(1,0),dfs_2(1,1);
	repi(i,1,m) si(x[i]),si(y[i]),si(z[i]),mx=max(mx,z[i]);
	repi(i,1,m){
		int lca=LCA(x[i],y[i]);
		add(rt[x[i]],1,mx,z[i],1),add(rt[y[i]],1,mx,z[i],1);
		add(rt[lca],1,mx,z[i],-1);
		if(fa[lca]) add(rt[fa[lca]],1,mx,z[i],-1);
	}
	dfs_3(1,0);
	repi(i,1,n) printf("%d\n",ans[i]);
	return 0;
}

暂且先记录到这,好像还有什么线段树分裂/分治 一块补掉再记录吧
坑越挖越多

发布了6 篇原创文章 · 获赞 1 · 访问量 100

猜你喜欢

转载自blog.csdn.net/qq_44684888/article/details/105591042