1. 程式人生 > 實用技巧 >關於樹套樹的 權值樹狀陣列套vector 實現

關於樹套樹的 權值樹狀陣列套vector 實現

Warning:本篇分享只是一個 較複雜方法 的 簡單寫法,不進行系統的講解。

前置知識:

  • AcWing 242. 一個簡單的整數問題
  • AcWing 244. 謎一樣的牛(樹狀陣列上倍增 解法,藍書裡有講)
  • 會寫 權值樹狀陣列。
  • 會用 vector 水平衡樹。(其實就是 insert(),erase() 常數小)
  • 會寫 權值線段樹套下標平衡樹。(藍書也有)

vector 實現的平衡樹

namespace Vtree
{
	void print(vector<int>& v)
	{
		for(int i=0;i<(int)v.size();i++) 	
			printf("%d ",v[i]);
		printf("\n");
	}
	void ins(vector<int>& v,int x) { v.insert(lower_bound(v.begin(),v.end(),x),x); }
	void del(vector<int>& v,int x) { v.erase(lower_bound(v.begin(),v.end(),x)); }
	int count(vector<int>& v,int l,int r)
	{
//		printf("count(%d ,%d) : ",l,r);
//		print(v);
		return upper_bound(v.begin(),v.end(),r)-lower_bound(v.begin(),v.end(),l);
	}	

}

權值線段樹套下標平衡樹解法:

先咕咕咕了。
...
然後你可能就會想,為什麼不把 權值線段樹 換成 樹狀陣列 , 把平衡樹換成 vector 呢?

  • 首先 vector 可以實現平衡樹(本題需要的)所有操作。

    • 插入
    • 刪除
    • 查區間元素個數
  • 線段樹的 查 rank = 樹狀陣列 字首和.

  • 線段樹的 線段樹上二分找 kth = 樹狀陣列倍增找 kth.

權值樹狀陣列套vector 解法

可能配一張圖會有助於理解

圖片來自 https://blog.csdn.net/flushhip/article/details/79165701 侵刪。

\(v[i]\) 存的是 \(a[i] \in [i-lowbit(i)+1,i]\)

\(i\) ,即下標。

insert

void insert(int pos,int key)
{
	for(;key<=num;key+=lowbit(key)) 
		Vtree::ins(v[key],pos);
}

根據定義,在對應的 vector 里加入相應的數。

erase

void erase(int pos,int key)
{
	for(;key<=num;key+=lowbit(key))
		Vtree::del(v[key],pos);
}

刪除操作。

Rank

Rank(x,l,r) 是找 下標在 \([l,r]\) 中有多少個數的 \(a[i] \leq x\)


對應在樹狀陣列中就是 以 count(v[i],l,r)i 的值,求一遍字首和即可。

int Rank(int x,int l,int r)
{
	int res=0;
	for(;x>=1;x-=lowbit(x)) 
		res+=Vtree::count(v[x],l,r);
	return res;
}

findkth

findkth(k,l,r) 是找 下標在 \([l,r]\) 中第 k 大的數。
樹狀陣列上倍增即可。

int findkth(int k,int l,int r)
{
	static const int lgn=log2(num);
	int key=0,sum=0;
	for(int i=lgn,y;i>=0;i--) {
		y=Vtree::count(v[key+(1<<i)],l,r);
		if(sum+y<k && key+(1<<i)<=num) sum+=y,key+=(1<<i);
	}
	return key+1;
}

這裡有兩點要注意

  1. 先跳到目標位置的前一個位置,否則可能會多跳目標位置後的空白段。
  2. 特判越界的情況。

找 前驅後繼 可以用以上兩個操作實現,
於是就寫完了。

namespace Vtree
{
	void ins(vector<int>& v,int x) { v.insert(lower_bound(v.begin(),v.end(),x),x); }
	void del(vector<int>& v,int x) { v.erase(lower_bound(v.begin(),v.end(),x)); }
	int count(vector<int>& v,int l,int r)
	{
		return upper_bound(v.begin(),v.end(),r)-lower_bound(v.begin(),v.end(),l);
	}	
}

inline int lowbit(int x) { return x&(-x); }
void insert(int pos,int key)
{
	for(;key<=num;key+=lowbit(key)) 
		Vtree::ins(v[key],pos);		
}
void erase(int pos,int key)
{
	for(;key<=num;key+=lowbit(key))
		Vtree::del(v[key],pos);
}
int Rank(int x,int l,int r)
{
	int res=0;
	for(;x>=1;x-=lowbit(x)) 
		res+=Vtree::count(v[x],l,r);
	return res;
}
int findkth(int k,int l,int r)
{
	static const int lgn=log2(num);
	int key=0,sum=0;
	for(int i=lgn,y;i>=0;i--) {
		y=Vtree::count(v[key+(1<<i)],l,r);
		if(sum+y<k && key+(1<<i)<=num) 
			sum+=y,key+=(1<<i);
	}
	return key+1;
}

AcWing 2476. 樹套樹
Code:

#include<set>
#include<map>
#include<queue>
#include<stack>
#include<ctime>
#include<cmath>
#include<bitset>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<complex>
#include<iostream>
#include<algorithm>

using namespace std;
typedef long long LL;
typedef unsigned long long ULL;

namespace Vtree
{
	void print(vector<int>& v)
	{
		for(int i=0;i<(int)v.size();i++) 	
			printf("%d ",v[i]);
		printf("\n");
	}
	void ins(vector<int>& v,int x) { v.insert(lower_bound(v.begin(),v.end(),x),x); }
	void del(vector<int>& v,int x) { v.erase(lower_bound(v.begin(),v.end(),x)); }
	int count(vector<int>& v,int l,int r)
	{
//		printf("count(%d ,%d) : ",l,r);
//		print(v);
		return upper_bound(v.begin(),v.end(),r)-lower_bound(v.begin(),v.end(),l);
	}	

}

const int N=1e5+5;

int num;
vector<int> v[N];

inline int lowbit(int x) { return x&(-x); }
void insert(int pos,int key)
{
	for(;key<=num;key+=lowbit(key)) {
//		printf("insert in v[%d], pos = %d\n",key,pos);
		Vtree::ins(v[key],pos);
	}
}
void erase(int pos,int key)
{
	for(;key<=num;key+=lowbit(key))
		Vtree::del(v[key],pos);
}
int Rank(int x,int l,int r)
{
//	printf("Rank val %d in (%d , %d) = ",x,l,r);
	int res=0;
	for(;x>=1;x-=lowbit(x)) 
		res+=Vtree::count(v[x],l,r);
//	printf("%d\n",res);
	return res;
}
int findkth(int k,int l,int r)
{
	static const int lgn=log2(num);
	int key=0,sum=0;
	for(int i=lgn,y;i>=0;i--) {
	//	printf("count in v[%d], (%d ,%d)\n",key+(1<<i),l,r);
		y=Vtree::count(v[key+(1<<i)],l,r);
		if(sum+y<k && key+(1<<i)<=num) {
			sum+=y,key+=(1<<i);
	//		printf("Accept %d , now key is %d and sum is %d\n",y,key,sum);
		}
	}
	return key+1;
}

vector<int> nums;
inline int getnw(int x)
{
	return upper_bound(nums.begin(),nums.end(),x)-nums.begin();	
} 

struct Query
{
	int opt,x,y,z;
}q[N];

int n,m;
int a[N];

int main()
{
//	freopen("1.in","r",stdin);
	int i;
	int opt,x,y,z;
	
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++) {
		scanf("%d",&a[i]);
		nums.push_back(a[i]);
	}
	for(i=1;i<=m;i++) {
		scanf("%d%d%d",&q[i].opt,&q[i].x,&q[i].y);
		if(q[i].opt^3) scanf("%d",&q[i].z);
		if(q[i].opt^2) {
			if(q[i].opt^3) nums.push_back(q[i].z);
			else nums.push_back(q[i].y);
		}
	}
	sort(nums.begin(),nums.end());
	nums.erase(unique(nums.begin(),nums.end()),nums.end());
	num=nums.size()+1;
	for(i=1;i<=n;i++) 
		insert(i,a[i]=getnw(a[i]));
	for(i=1;i<=m;i++) {
		if(q[i].opt^2) {
			if(q[i].opt^3) q[i].z=getnw(q[i].z);
			else q[i].y=getnw(q[i].y);
		}
	}
		
	for(i=1;i<=m;i++) {
		opt=q[i].opt; x=q[i].x; y=q[i].y; z=q[i].z;
		
		if(opt==1) printf("%d\n",Rank(z-1,x,y)+1);
		else if(opt==2) printf("%d\n",nums[findkth(z,x,y)-1]);
		else if(opt==3) erase(x,a[x]),insert(x,a[x]=y);
		else if(opt==4) {
			int t=Rank(z-1,x,y);
			if(t==0) puts("-2147483647");
			else printf("%d\n",nums[findkth(t,x,y)-1]);
		}
		else {
			int t=Rank(z,x,y);
			if(t==y-x+1) puts("2147483647");
			else printf("%d\n",nums[findkth(t+1,x,y)-1]);
		}
	}
	return 0;
}

效能分析:
由於使用了vector當平衡樹,時間複雜度不好分析,
空間複雜度是 \(O(nlogn)\)
這種解法在洛谷的樹套樹中,是最優解第一面中程式碼最短的。可以說,在分塊遍地開花的世界中獨樹一幟了。

進步性:程式碼短、快,便於除錯。
侷限性:樹狀陣列的空間受限於值域,如果強制線上,並且值域很大的話,就不能用這種方法來維護了。