淺談動態開點線段樹
阿新 • • 發佈:2018-12-02
興致勃勃的弱雞想去學主席樹了!
可惜突然發現動態開點線段樹不會了。。。。
發揮大膽猜想,不用求證的精神:你們都會線段樹
眾所周知:線段樹空間複雜度是O4*n,這就不用證,因為我菜
那當n很大,操作正常時,如果像正常的線段樹一樣(記左兒子為n+n,右兒子為n+n+1),再開四倍n會炸記憶體
怎麼辦呢?
想一想:時間複雜度與原來幾乎差不多 mlg n,與原來差距只有沒幾倍的常數而已
但是為什麼每次操作時間才lg n?
因為每次只會經過·lg n個結點,也就是總共最多經過m lg n個結點
再m lg n遠遠小於4n的情況下,再開4n的結點不是很浪費嗎?
不一定每個點都有用
所以就可以動態開點啦!
記左兒子lc,右兒子rc就夠了
例題:Luogu P1908 逆序對
https://www.luogu.org/problemnew/show/P1908
就是求個逆序對
可以離散化後樹狀陣列,線段樹
但是可以用動態開點線段樹!
按上文的說法,nlgn的空間,開3個數組,必炸記憶體,但是我只開了1e7的陣列還是過了,
哈哈哈哈
#include<cstdio> #define ll long long using namespace std; const int N=1e7+7; //就這麼點 int n,r; ll ans; struct A { int cnt,c[N],lc[N],rc[N]; int sum(int p,int l,int r,int x,int y) { if(!p) return 0; if(l==x&&r==y) return c[p]; int mid=l+r>>1; if(y<=mid) return sum(lc[p],l,mid,x,y); if(x>mid) return sum(rc[p],mid+1,r,x,y); return sum(lc[p],l,mid,x,mid)+sum(rc[p],mid+1,r,mid+1,y); } void add(int &p,int l,int r,int x,int k) { if(!p) p=++cnt; if(l==r) { c[p]+=k; return; } int mid=l+r>>1; if(x<=mid) add(lc[p],l,mid,x,k); else add(rc[p],mid+1,r,x,k); c[p]=c[lc[p]]+c[rc[p]]; } }tree; int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { int x; scanf("%d",&x); ans+=tree.sum(r,1,1e9,x+1,1e9); tree.add(r,1,1e9,x,1); } printf("%lld",ans); return 0; }