动态开点线段树
Posted virtu0s0
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态开点线段树相关的知识,希望对你有一定的参考价值。
用途
- 需要建立多棵独立的线段树
- 线段树维护的值域较大(1e9),但是操作次数较少(1e5)
特征
- 类似主席树的原理,动态分配每个树节点的位置(lson[],rson[]),每次只更新一条链,但是主席树是建立一颗新的树,动态开点线段树是在一棵树上不断添加节点(还是一棵树)
- 类似线段树的原理,push_down区间修改,push_up区间查询
例题
1.维护值域较大,线段树区间修改
cf915e
https://codeforces.com/contest/915/problem/E
题意:
q(3e5)次区间修改,将区间修改成0或1,每次修改后输出1~n(1e9)的和
题解:
- 动态加点线段树区间修改练习
- 通常的空间是修改次数*40(和主席树相同),根据re(少)和mle(多)修改空间
代码
#include<bits/stdc++.h>
#define all 1000000000
#define M 400005
using namespace std;
int n,q,ly[M*40],sum[M*40],ls[M*40],rs[M*40],l,r,k,dfn=0,rt=0;
void push_down(int o,int l,int r){
if(ly[o]>=0){
if(!ls[o])ls[o]=++dfn;
if(!rs[o])rs[o]=++dfn;
int mid=(l+r)/2;
ly[ls[o]]=ly[rs[o]]=ly[o];
sum[ls[o]]=ly[ls[o]]*(mid-l+1);
sum[rs[o]]=ly[rs[o]]*(r-mid);
ly[o]=-1;
}
}
void ud(int &o,int l,int r,int L,int R,int x){
if(!o)o=++dfn;
if(L<=l&&r<=R){
ly[o]=x;
sum[o]=ly[o]*(r-l+1);
return;
}
push_down(o,l,r);
int mid=(l+r)/2;
if(L<=mid)ud(ls[o],l,mid,L,R,x);
if(R>mid)ud(rs[o],mid+1,r,L,R,x);
sum[o]=sum[ls[o]]+sum[rs[o]];
}
int main(){
cin>>n>>q;
memset(ly,-1,sizeof(ly));
while(q--){
scanf("%d%d%d",&l,&r,&k);
if(k==1)ud(rt,1,all,l,r,1);
else ud(rt,1,all,l,r,0);
printf("%d
",n-sum[1]);
}
}
2.需要建立多棵线段树
1046a
https://codeforces.com/contest/1046/problem/A
题意:
n(1e5)个机器人,每个机器人有位置x,半径r,智商值q(都是1e9),
设两个机器人能相互识别的条件是:两个机器人在彼此半径中,并且智商值相差不超过k(20),问有多少对机器人能互相识别
题解:
- 将半径从大到小的顺序加入机器人(单点修改),线段树维护位置上机器人的个数(1e9),在加入前查询在这个机器人范围内的机器人个数(区间查询),这样做可以保证统计出来的机器人一定在相互范围内(因为后面的能看到前面的,前面的一定也能看到后面的)
- 因为k只有20,所以可以考虑暴力,每一个智商值建一颗线段树,然后暴力枚举[q-k,q+k],范围内的线段树
- 注意离散化智商值
代码:
#include<bits/stdc++.h>
#define ll long long
#define M 100005
using namespace std;
struct N{
ll x,r,q;
}p[M];
int n,k,cnt=0,dfn=0,rt[M*40],rs[M*40],ls[M*40];
ll l,r,a,b,i,j,all=1e9,sum[M*40],ans;
map<ll,int>mp;
bool cmp(N a,N b){
return a.r>b.r;
}
void ud(int &o,int l,int r,int p,int x){
if(!o)o=++dfn;
sum[o]+=x;
if(l==r)return;
int mid=(l+r)/2;
if(p<=mid)ud(ls[o],l,mid,p,x);
else ud(rs[o],mid+1,r,p,x);
}
ll qy(int o,int l,int r,int L,int R){
if(!o)return 0;
if(L<=l&&r<=R)return sum[o];
int mid=(l+r)/2;
ll ans=0;
if(L<=mid)ans+=qy(ls[o],l,mid,L,R);
if(R>mid)ans+=qy(rs[o],mid+1,r,L,R);
return ans;
}
int main(){
cin>>n>>k;
for(i=0;i<n;i++){
cin>>p[i].x>>p[i].r>>p[i].q;
}
sort(p,p+n,cmp);
for(i=0;i<n;i++){
l=max(0ll,p[i].x-p[i].r);
r=min(all,p[i].x+p[i].r);
a=max(0ll,p[i].q-k);
b=p[i].q+k;
for(j=a;j<=b;j++){
if(mp.find(j)==mp.end())continue;
ans+=qy(rt[mp[j]],0,all,l,r);
}
if(mp[p[i].q]==0)mp[p[i].q]=++cnt;
ud(rt[mp[p[i].q]],0,all,p[i].x,1);
}
cout<<ans;
}
3.在线段树上dp
1111c
https://codeforces.com/contest/1111/problem/C
题意:
合并一段2^n(30)的区间,一种方法是对半分成两个区间合并,另一种方法是直接合并,代价是Ba[l,r](r-l+1)(有人)or A(没人),一共有k(1e5)个人
题解:
- 转移方程:
f[o]=min(a[o](r-l+1)B,f[ls[o]]+f[rs[o]])
代码:
#include<bits/stdc++.h>
#define M 100005
#define ll long long
using namespace std;
int ls[M*40],rs[M*40],rt=0,all,n,A,B,k,x,i,dfn=0;
ll a[M*40],f[M*40];
void ud(int &o,int l,int r,int p){
if(!o)o=++dfn;
a[o]++;
if(l==r){
f[o]=B*a[o];
return;
}
int mid=(l+r)/2;
if(p<=mid)ud(ls[o],l,mid,p);
else ud(rs[o],mid+1,r,p);
if(a[ls[o]]==0)f[ls[o]]=A;
if(a[rs[o]]==0)f[rs[o]]=A;
f[o]=min((r-l+1)*a[o]*B,f[ls[o]]+f[rs[o]]);
}
int main(){
cin>>n>>k>>A>>B;
all=1<<n;
for(i=0;i<k;i++){
scanf("%d",&x);
ud(rt,1,all,x);
}
printf("%lld
",f[1]);
}
以上是关于动态开点线段树的主要内容,如果未能解决你的问题,请参考以下文章