对于动态开点的线段树来说,当有很多棵线段树时,我们可以进行线段树合并,线段树合并经常用于多个点的贡献,合并两棵线段树的复杂度为两棵线段树相同节点的数m*logm。
将y这棵线段树合并到x这棵线段树,对于x没有y有的节点,直接复制y的就行,x有y没有的不用管,都有的加起来即可,这样复杂度就是x与y相同都有的点数。
线段树分裂一般用于一些区间操作,把一个区间单独拉出来成树,将这棵线段树分裂成一些线段树,找要用的那棵。分裂的复杂度为严格的log。
struct node{ //权值线段树
int l,r;
ll num;
}a[maxn*20];
int root[maxn],cnt,top = 2e5;
vector<int> dump;
int newnode() //新建一个节点
{
if( dump.empty() ) return ++cnt;
else
{
int res = dump.back();
dump.pop_back();
return res;
}
}
void del(int p) //这个点没用了可以删除
{
a[p].l = a[p].r = a[p].num = 0;
dump.push_back(p);
}
void update(int x)
{
a[x].num = a[a[x].l].num+a[a[x].r].num;
}
void modify(int &now,int l,int r,int p,ll v) //动态开点
{
if( now == 0 ) now = newnode();
if( l == r )
{
a[now].num += v;
return;
}
int m = (l+r)>>1;
if( p <= m ) modify(a[now].l,l,m,p,v);
else modify(a[now].r,m+1,r,p,v);
update(now);
}
void merge(int &x,int y,int l,int r) //线段树合并
{
if( y == 0 ) return;
if( x == 0 )
{
x = y;
return;
}
if( l == r )
{
a[x].num += a[y].num;
return;
}
int m = (l+r)>>1;
merge(a[x].l,a[y].l,l,m);merge(a[x].r,a[y].r,m+1,r);//合并左右子树,传的是引用所以儿子关系都弄好了
del(y);
update(x);
}
void split(int &x,int &y,ll k) //将x这棵线段树分为两棵,前k个留在x树,剩下的给y树
{
if( x == 0 ) return;
y = newnode(); //y需要新建节点
ll v = a[a[x].l].num; //x左子树的数量
if( k > v ) split(a[x].r,a[y].r,k-v); //k大于它,那么左子树留在x上,分裂右子树
else a[y].r = a[x].r,a[x].r = 0; //否则右子树给y,x的右子树消失
if( k < v ) split(a[x].l,a[y].l,k); //k小于v时才需要继续分裂左子树,等于的时候就不用了
a[y].num = a[x].num - k; //y的数量有x的数量减k
a[x].num = k; //分裂后x就只剩下k个了
}