主席树总结

静态区间第k大

/*
主席树的本质就是动态开点,维护前缀的信息,从而实现了维护多棵线段树。
保存新版本时,复制上一个版本,然后附加上需要修改的儿子,空间为log 
静态区间第k大 
维护n个权值线段树,第i个权值线段树表示[1...i]这些数构成的权值线段树
对于第i个权值线段树,只需要用第i-1的根,再维护那个改变的值,所用的空间为log 
查询区间第k小时用R的权值线段树减去L-1的权值线段树即可 
*/ 

//静态区间第k大 
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

const int maxn = 2e5+5;

struct node{
	int l,r,sum;    //权值线段树,左右儿子及总和 
}hjt[maxn*40];

int a[maxn],root[maxn],cnt;
vector<int> v;

int get_id(int x)
{
	return lower_bound(v.begin(),v.end(),x) - v.begin() + 1;  //离散化,下标从1开始 
}

void insert(int l,int r,int pre,int &now,int p)  //当前区间为[l,r],当前的节点为now,pre为前一个节点,插入p 
{
	hjt[++cnt] = hjt[pre];   //使用新节点,将前一个节点复制过来 
	now = cnt;           //需要修改now值,所以传引用 
	hjt[now].sum ++;    //增加一个新元素sum值++ 
	if( l == r ) return;
	int m = ( l + r ) >> 1;
	if( p <= m ) insert(l,m,hjt[pre].l,hjt[now].l,p); //要插入的元素在左区间,意味着右区间now与pre其实是相等的 
	else insert(m+1,r,hjt[pre].r,hjt[now].r,p); 
} 

int query(int l,int r,int L,int R,int k)   //当前区间为[l,r],要查询的区间的左右端点为L和R,查询第k大
{
	if( l == r ) return l;   //左右区间相等,只剩一个数了
	int m = (l + r) >> 1;
	int tmp = hjt[hjt[R].l].sum - hjt[hjt[L].l].sum;  //计算左子树的元素个数
	if( k <= tmp ) return query(l,m,hjt[L].l,hjt[R].l,k);
	else return query(m+1,r,hjt[L].r,hjt[R].r,k-tmp); 
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int n,m;
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
	{
		cin >> a[i];
		v.push_back(a[i]);
	}
	sort(v.begin(),v.end());   //排序 
	v.erase(unique(v.begin(),v.end()),v.end());  //去重 
	for (int i = 1; i <= n; i++)
	{
		insert(1,n,root[i-1],root[i],get_id(a[i]));   //插入离散化后的值 
	} 
	for (int i = 1; i <= m; i++)
	{
		int l,r,k;
		cin >> l >> r >> k;
		cout << v[query(1,n,root[l-1],root[r],k)-1] << '\n';
	} 
	return 0;
}

主席树的区间修改

单点修改时,直接跑到叶子节点,修改的节点只有log个,但是区间修改的话如果跑到叶子肯定是不行的,懒标记的存在使得下传时存在问题,一种操作是下传时新建节点,由于操作只有nlogn,新增的点数量也不会太多,就是空间不大好。但是其实有些情况是可以在访问时维护这个值,比如维护区间极值,每次修改的值都比之前的大,那么我们就不用下传标记,直接在访问的过程中维护懒标记的极值。

动态区间第k大

/*
支持单点修改的区间第k大
主席树套树状数组
静态区间第k大,每棵主席树维护的是前缀的权值线段树
现在由于需要修改,所以我们需要将树状数组的思想引入,log修改单点,log查询前缀和
第i棵线段树维护[i-lowbit(i),i]这一区间的权值线段树,修改时改log次即可,查询时求和log棵线段树 
时间复杂度n(logn)^2,不离散化的话有一个log是log1e9,但是方便写很多 
*/

#include <iostream>
#include <vector>
using namespace std;

const int maxn = 1e5 + 5;

struct node{
	int l,r;
	int sum;
}hjt[maxn*400];    //每次修改是log次,所以空间需要开大一些,极限情况下是log1e5*log1e9,由于动态开点所以没有离散化 

int root[maxn],a[maxn],cnt;
int n;

void insert(int l,int r,int pre,int &now,int p,int v)  //当前区间为[l,r],p位置加上v 
{
	if( !now ) now = ++cnt;   //如果now为0才需要给新的节点,节约空间,动态开点时都这么写!!! 
	hjt[now] = hjt[pre];
	hjt[now].sum += v;
	if( l == r ) return;
	int m = ( l + r ) >> 1;
	if( p <= m ) insert(l,m,hjt[pre].l,hjt[now].l,p,v);
	else insert(m+1,r,hjt[pre].r,hjt[now].r,p,v);  
}

int lowbit(int x)
{
	return x & -x;
}

void update(int x,int pos,int v)
{
	for (int i = x; i <= n; i += lowbit(i))    
	{
		insert(0,1e9,root[i],root[i],pos,v);   //类比树状数组的操作,那时候的c[i]数组,这时候的第i棵线段树 
	}
}

vector<int> id[2];   //当前访问的左右区间对应的线段树节点的编号 

int cal(int index,int flag)   //计算某一子树的前缀和 
{
	int res = 0;
	for (int i = 0; i < id[index].size(); i++)   
	{
		if( flag == 0 ) res += hjt[hjt[id[index][i]].l].sum;
		else res += hjt[hjt[id[index][i]].r].sum;
	}
	return res;
}

int query(int l,int r,int k)   //当前区间为[l,r],查询第k小 
{
	if( l == r ) return l;   //左右区间相等,只剩一个数了
	int m = (l + r) >> 1;
	int tmp = cal(0,0) - cal(1,0);  //计算左子树的元素个数
	if( k <= tmp ) 
	{
		for (int i = 0; i < 2; i++)   //所有的前缀节点都要变为其左儿子 
		{
			for (int j = 0; j < id[i].size(); j++) id[i][j] = hjt[id[i][j]].l;
		}
		return query(l,m,k);
	}
	else 
	{
		for (int i = 0; i < 2; i++)   //所有的前缀节点都要变为其右儿子 
		{
			for (int j = 0; j < id[i].size(); j++) id[i][j] = hjt[id[i][j]].r;
		}
		return query(m+1,r,k-tmp); 
	}
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int m;
	cin >> n >> m;
	for (int i = 1; i <= n; i++) cin >> a[i];
	for (int i = 1; i <= n; i++)
	{
		update(i,a[i],1);
	}
	for (int i = 1; i <= m; i++)
	{
		char op;
		cin >> op;
		if( op == 'Q' )
		{
			int l,r,k;
			cin >> l >> r >> k;
			id[0].clear();
			id[1].clear();
			for (int j = r; j > 0; j -= lowbit(j) ) id[0].push_back(root[j]);
			for (int j = l-1; j > 0; j -= lowbit(j) ) id[1].push_back(root[j]);
			//这里十分关键!!!由于我们在查询第k小的过程中需要查找线段树上某一区间的前缀和,
			//但是我们并不知道这些前缀的节点编号,所以我们需要维护树状数组求和的过程中用到的节点,
			//在去其子树的时候一起更新 
			cout << query(0,1e9,k) << '\n';
		}else
		{
			int x,y;
			cin >> x >> y;
			update(x,a[x],-1);
			update(x,y,1);
			a[x] = y;
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值