1. 程式人生 > >淺談動態開點線段樹

淺談動態開點線段樹

興致勃勃的弱雞想去學主席樹了!

可惜突然發現動態開點線段樹不會了。。。。

發揮大膽猜想,不用求證的精神:你們都會線段樹

眾所周知:線段樹空間複雜度是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;
}