线段树合并

一日双更了属于是,后续可能会把二分,分裂和分治也写到这里来。

前置:动态开点权值线段树。

在学完了dsu on tree 之后,我们就能在题解区发现线段树合并算法了(雾)。线段树合并,顾名思义也是线段树上的一种操作,其实他的内容就一个函数,很好理解。

我们考虑现在有两颗动态开点线段树,我们要合并他们的信息,这个信息可能是什么 min/max 什么什么乱七八糟的东西,只要能合并就行。

一个基本模版。

inline void merge(ll &rt1,ll rt2,ll l,ll r)
{
	if(!rt1) return rt1=rt2,void();if(!rt2) return;
	if(l==r){/*do something*/return;}
	ll mid=l+r>>1;
	merge(tree[rt1].l,tree[rt2].l,l,mid);
	merge(tree[rt1].r,tree[rt2].r,mid+1,r);
	pushup(rt1);
}

此处是将 rt2 合并到 rt1 这颗树上,还是很好理解的,这种写法是遍利到叶子节点再回溯,通过 pushup 来更新答案的。

线段树合并的复杂度是 O(nlogn),为什么?每次都合并两棵满二叉肯定是不行的,线段树合并适用于合并点数或者说是次数 105 的,假设我们有 n 棵权值线段树,每一棵里面存了一个数,也就是有 logn 个点,考虑把这些树合并到一起,简单模拟能够发现,我们一共合并 n1,最慢的方式就是每次两两合并对吧,这样的话,我们按照线段树上每个点的遍利次数来看,第一层也就是根节点被遍利 n 次,第二层有两个点,每一个被遍利 n2 次,第三层同理,一共有 logn 层,所以复杂度为 O(nlogn),带一个大常数。

例题1:P3224 [HNOI2012] 永无乡

n 个数初始构成一堆集合,每个数有权值,支持两个操作,合并两个集合,查询某个点所在集合第 k 大的值。

第一个操作很显然可以用并查集 + 线段树合并完成,第二个操作就是线段树上二分,就做完了?

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f3f3f3f3f
#define inf 0x3f3f3f3f
#define pr putchar('\n')
#define fi first
#define se second
#define pp putchar(' ')
#define pii pair<ll,ll>
#define pdi pair<ll,ll>
#define mem(aa,bb) memset(aa,bb,sizeof(aa))
#define fo(a,i,b) for(register ll i = a ; i <= b ; ++ i )
#define Fo(a,i,b) for(register ll i = a ; i >= b ; -- i )
#define pb push_back
//#pragma GCC optimize(2)
using namespace std;
//typedef int ll;
typedef long long ll;
//typedef __int128 ll;
typedef double db;
inline void read(ll &opp){ll x=0,t=1;char ch;ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-'){t=-1;}ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}opp=x*t;return; }
inline void wr(ll x){if(x<0){putchar('-');x=-x;}if(x>9){wr(x/10);}putchar(x%10+'0');}
const ll N=1e6+5,M=2e4+5,mod=1e9+7;
ll n,m,p[N],rot[N],cnt,fa[N],q,id;
struct SGT{ll l,r,sum,cnt;}tree[N<<2];
#define rt tree[root]
#define ls tree[tree[root].l]
#define rs tree[tree[root].r]
inline void pushup(ll root){rt.cnt=ls.cnt+rs.cnt;}
inline void upd(ll &root,ll l,ll r,ll x,ll k)
{
	if(!root) root=++cnt;
	if(l==r) return rt.cnt=1,rt.sum=k,void();
	ll mid=l+r>>1;if(x<=mid) upd(rt.l,l,mid,x,k);else upd(rt.r,mid+1,r,x,k);
	pushup(root);
}
inline void merge(ll &t1,ll t2,ll l,ll r)
{
	if(!t1) return t1=t2,void();if(!t2) return;
	if(l==r) 
	{
		if(tree[t2].sum) tree[t1].sum=tree[t2].sum;
		tree[t1].cnt+=tree[t2].cnt;
		return;
	}
	ll mid=l+r>>1;
	merge(tree[t1].l,tree[t2].l,l,mid);
	merge(tree[t1].r,tree[t2].r,mid+1,r);
	pushup(t1);
}
inline ll ask(ll root,ll l,ll r,ll k)
{
	if(rt.cnt<k) return -1;
	if(l==r) return rt.sum;
	ll mid=l+r>>1;
	if(ls.cnt>=k) return ask(rt.l,l,mid,k);
	else return ask(rt.r,mid+1,r,k-ls.cnt);
}
inline ll Find(ll x){return fa[x]==x?x:fa[x]=Find(fa[x]);}
inline void Link(ll x,ll y){ll xx=Find(x),yy=Find(y);if(xx==yy) return;fa[xx]=yy;}
inline void Merge(ll u,ll v)
{
	if(Find(u)==Find(v)) return;
	merge(rot[Find(v)],rot[Find(u)],1,n);Link(u,v);
}
signed main(){
	read(n),read(m);
	fo(1,i,n) read(p[i]),upd(rot[i],1,n,p[i],i),fa[i]=i;
	fo(1,i,m){ll u,v;read(u),read(v);Merge(u,v);}
	read(q);
	fo(1,i,q)
	{
		char c;cin>>c;
		ll x,y;id++;read(x),read(y);
		if(c=='Q') wr(ask(rot[Find(x)],1,n,y)),pr;
		if(c=='B') Merge(x,y);
	}
    return 0;
}

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

一棵树,m 次修改,对一条路径上每个点添加一个值 z,最后求每个点的权值众数。

首先可以对路径添加做差分转化,对 xy 加一,对 lcalca 的父亲减一就可以了,这样每个点答案就变成他的子树内的众数。只需要最后从下到上遍利,每次合并这个点和他的儿子就好了,复杂度 O(nlogn)

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f3f3f3f3f
#define inf 0x3f3f3f3f
#define pr putchar('\n')
#define fi first
#define se second
#define pp putchar(' ')
#define pii pair<ll,ll>
#define pdi pair<ll,ll>
#define mem(aa,bb) memset(aa,bb,sizeof(aa))
#define fo(a,i,b) for(register ll i = a ; i <= b ; ++ i )
#define Fo(a,i,b) for(register ll i = a ; i >= b ; -- i )
#define pb push_back
//#pragma GCC optimize(2)
using namespace std;
typedef int ll;
// typedef long long ll;
//typedef __int128 ll;
typedef double db;
inline void read(ll &opp){ll x=0,t=1;char ch;ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-'){t=-1;}ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}opp=x*t;return; }
inline void wr(ll x){if(x<0){putchar('-');x=-x;}if(x>9){wr(x/10);}putchar(x%10+'0');}
const ll N=2e6+5,M=2e4+5,mod=1e9+7;
ll n,m,siz[N],fa[N],dep[N],top[N],w[N],id[N],son[N],cnt,opt,ans[N],rot[N];
vector<ll> g[N];
struct SGT{ll l,r,sum,id;}tree[N<<2];
#define rt tree[root]
#define ls tree[tree[root].l]
#define rs tree[tree[root].r]
inline void pushup(ll root){if(ls.sum>rs.sum) rt.sum=ls.sum,rt.id=ls.id;else if(ls.sum<rs.sum) rt.sum=rs.sum,rt.id=rs.id;else rt.sum=ls.sum,rt.id=min(ls.id,rs.id);}
inline void upd(ll &root,ll l,ll r,ll x,ll k){
	if(!root) root=++opt;
	if(l==r){rt.sum+=k;rt.id=l;return;}
	ll mid=l+r>>1;if(x<=mid) upd(rt.l,l,mid,x,k);else upd(rt.r,mid+1,r,x,k);
	pushup(root);
}
inline void merge(ll &rt1,ll rt2,ll l,ll r)
{
	if(!rt1) return rt1=rt2,void();if(!rt2) return;
	if(l==r){tree[rt1].sum+=tree[rt2].sum;tree[rt1].id=l;return;}
	ll mid=l+r>>1;
	merge(tree[rt1].l,tree[rt2].l,l,mid);
	merge(tree[rt1].r,tree[rt2].r,mid+1,r);
	pushup(rt1);
}
inline void dfs1(ll x,ll fat,ll depth){dep[x]=depth,fa[x]=fat,siz[x]=1;for(ll y:g[x]){if(y==fa[x]) continue;dfs1(y,x,depth+1);siz[x]+=siz[y];if(siz[y]>siz[son[x]]) son[x]=y;}}
inline void dfs2(ll x,ll nowtop){top[x]=nowtop,id[x]=++cnt,w[cnt]=x;if(!son[x]) return;dfs2(son[x],nowtop);for(ll y:g[x]){if(y==fa[x]||y==son[x]) continue;dfs2(y,y);}}
inline ll LCA(ll x,ll y){while(top[x]^top[y]) dep[top[x]]<dep[top[y]]?y=fa[top[y]]:x=fa[top[x]];return dep[x]<dep[y]?x:y;}
inline void dfs(ll x,ll fa){
	for(ll y:g[x])
	{
		if(y==fa) continue;
		dfs(y,x),merge(rot[x],rot[y],1,1e5);
	}
	// wr(x),pp,wr(tree[rot[x]].sum),pr;
	ans[x]=tree[rot[x]].id;
}
signed main(){
	read(n),read(m);fo(1,i,n-1){ll u,v;read(u),read(v);g[u].pb(v),g[v].pb(u);}
	dfs1(1,0,1),dfs2(1,1);
	fo(1,i,m){ll x,y,z;read(x),read(y),read(z);upd(rot[x],1,1e5,z,1),upd(rot[y],1,1e5,z,1),upd(rot[fa[LCA(x,y)]],1,1e5,z,-1),upd(rot[LCA(x,y)],1,1e5,z,-1);}
	dfs(1,0);fo(1,i,n) wr(ans[i]),pr;
    return 0;
}

P3605 [USACO17JAN] Promotion Counting P

模版,线段树合并的 O(nlogV) 写法比树状数组启发式合并 O(nlog2n) 的写法慢了一倍多。

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f3f3f3f3f
#define inf 0x3f3f3f3f
#define pr putchar('\n')
#define fi first
#define se second
#define pp putchar(' ')
#define pii pair<ll,ll>
#define pdi pair<ll,ll>
#define mem(aa,bb) memset(aa,bb,sizeof(aa))
#define fo(a,i,b) for(register ll i = a ; i <= b ; ++ i )
#define Fo(a,i,b) for(register ll i = a ; i >= b ; -- i )
#define pb push_back
//#pragma GCC optimize(2)
using namespace std;
typedef int ll;
// typedef long long ll;
//typedef __int128 ll;
typedef double db;
inline void read(ll &opp){ll x=0,t=1;char ch;ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-'){t=-1;}ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}opp=x*t;return; }
inline void wr(ll x){if(x<0){putchar('-');x=-x;}if(x>9){wr(x/10);}putchar(x%10+'0');}
const ll N=5e6+5,M=2e4+5,mod=1e9+7;
ll n,p[N],ans[N],cnt,rot[N],x;
vector<ll> g[N];
struct SGT{ll l,r,sum;}tree[N<<2];
#define rt tree[root]
#define ls tree[tree[root].l]
#define rs tree[tree[root].r]
inline void pushup(ll root){rt.sum=ls.sum+rs.sum;}
inline void upd(ll &root,ll l,ll r,ll x,ll k){if(!root) root=++cnt;if(l==r) return rt.sum+=k,void();ll mid=l+r>>1;if(x<=mid) upd(rt.l,l,mid,x,k);else upd(rt.r,mid+1,r,x,k);pushup(root);}
inline void merge(ll &rt1,ll rt2,ll l,ll r){if(!rt1) return rt1=rt2,void();if(!rt2) return;if(l==r) return tree[rt1].sum+=tree[rt2].sum,void();ll mid=l+r>>1;merge(tree[rt1].l,tree[rt2].l,l,mid),merge(tree[rt1].r,tree[rt2].r,mid+1,r);pushup(rt1);}
inline ll ask(ll root,ll l,ll r,ll x,ll y){if(!root) return 0;if(x<=l&&y>=r) return rt.sum;ll mid=l+r>>1,ans=0;if(x<=mid) ans+=ask(rt.l,l,mid,x,y);if(y>mid) ans+=ask(rt.r,mid+1,r,x,y);return ans;}
inline void dfs(ll x,ll fa){for(ll y:g[x]) if(y!=fa) dfs(y,x),merge(rot[x],rot[y],1,1e9);ans[x]=ask(rot[x],1,1e9,p[x]+1,1e9);}
signed main(){
	read(n);fo(1,i,n) read(p[i]),upd(rot[i],1,1e9,p[i],1);
	fo(2,i,n) read(x),g[x].pb(i),g[i].pb(x);
	dfs(1,0);fo(1,i,n) wr(ans[i]),pr;
    return 0;
}
posted @   Wei_Han  阅读(28)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示