動態開點線段樹
用途
- 需要建立多棵獨立的線段樹
- 線段樹維護的值域較大(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",n-sum[1]); } }
題意:
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; }
題意:
合併一段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\n",f[1]); }