zoukankan      html  css  js  c++  java
  • 关于树套树的 权值树状数组套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("
    ");
    	}
    	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 解法

    可能配一张图会有助于理解
    b720737e6735c96e30b9e2dc52dd3820.png

    图片来自 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("
    ");
    	}
    	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
    ",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
    ",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)
    ",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
    ",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
    ",Rank(z-1,x,y)+1);
    		else if(opt==2) printf("%d
    ",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
    ",nums[findkth(t,x,y)-1]);
    		}
    		else {
    			int t=Rank(z,x,y);
    			if(t==y-x+1) puts("2147483647");
    			else printf("%d
    ",nums[findkth(t+1,x,y)-1]);
    		}
    	}
    	return 0;
    }
    

    性能分析:
    由于使用了vector当平衡树,时间复杂度不好分析,
    空间复杂度是 (O(nlogn))
    这种解法在洛谷的树套树中,是最优解第一面中代码最短的。可以说,在分块遍地开花的世界中独树一帜了。

    进步性:代码短、快,便于调试。
    局限性:树状数组的空间受限于值域,如果强制在线,并且值域很大的话,就不能用这种方法来维护了。

  • 相关阅读:
    【BZOJ 2124】【CodeVS 1283】等差子序列
    【BZOJ 1036】【ZJOI 2008】树的统计Count
    【BZOJ 1901】【ZJU 2112】Dynamic Rankings
    【BZOJ 3924】【ZJOI 2015】幻想乡战略游戏
    【BZOJ 4103】【THUSC 2015】异或运算
    【BZOJ 4513】【SDOI 2016】储能表
    【HDU 3622】Bomb Game
    【BZOJ 3166】【HEOI 2013】Alo
    【BZOJ 3530】【SDOI 2014】数数
    【BZOJ 4567】【SCOI 2016】背单词
  • 原文地址:https://www.cnblogs.com/cjl-world/p/14255060.html
Copyright © 2011-2022 走看看