初学主席树(可持久化线段树) —— 第 K 小数

Acwing

洛谷

在这里插入图片描述
一种船新的数据结构,可以保存多代信息的线段树。

具体思路很简单:每次单点修改(这里只涉及单点修改主席树,区间修改很难维护),树中只有一条 log 长度的路径上信息有影响,所以我们新建一条记录更改后的路径,把这条路径用指针的方法连回 上一个版本 的线段树。

然后每个版本都会对应一个根节点 r o o t [ i ] root[i] root[i] ,我们只需要从对应版本根节点遍历这颗树,就能获得对应版本的信息了。

对于此类问题的区间 [ l , r ] [l,r] [l,r] 询问,往往会涉及到前缀和的应用,如果 r o o t [ r ] root[r] root[r] 版本的树信息减去 r o o t [ l ] root[l] root[l] 版本的是可行的不影响的,显然具有前缀和性质。我们可以由此处理区间问题。

C o d e : Code: Code:

具体细节就是一些指针的运用比较独特。

#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
#define mem(a,b) memset(a,b,sizeof a)
#define cinios (ios::sync_with_stdio(false),cin.tie(0),cout.tie(0))
#define debug(x) cout<<"target is "<<x<<endl
#define forr(a,b,c) for(int a=b;a<=c;a++)
#define all(a) a.begin(),a.end()
#define oper(a) operator<(const a& ee)const
#define endl "\n"
#define ul (u << 1)
#define ur (u << 1 | 1)
using namespace std;

typedef long long ll;
typedef pair<int, int> PII;

const int N = 1e5 + 10, M = 100010, MM = 110;
int INF = 0x3f3f3f3f, mod = 1e9 + 7;
ll LNF = 0x3f3f3f3f3f3f3f3f;
int n, m, k;
int a[N];
vector<int> vec;//离散化
int find(int x) {
	return lower_bound(all(vec), x) - vec.begin() + 1;
}
struct tree
{
	int l, r;//主席树不会记录区间端点,存的是指针指向左右结点
	int cnt;//存数出现的个数
}tr[N * 25];
int idx, root[N];

int build(int l, int r) {
	int p = ++idx;
	if (l == r)return p;
	int mid = l + r >> 1;
	tr[p].l = build(l, mid);//连接左右
	tr[p].r = build(mid + 1, r);
	return p;
	//传回指针下标
}
int update(int pre, int l, int r, int x) {
	int p = ++idx;
	tr[p] = tr[pre]; tr[p].cnt++;//p代继承上一代的信息
	if (l == r)return p;
	int mid = l + r >> 1;

	//但自己那条更改的路径还是要保存连接,链状,对应更改指针即可
	if (x <= mid)tr[p].l = update(tr[pre].l, l, mid, x);
	else tr[p].r = update(tr[pre].r, mid + 1, r, x);
	return p;
}
int query(int p, int pre, int l, int r, int k) {
	if (l == r)return r;
	int x = tr[tr[p].l].cnt - tr[tr[pre].l].cnt;
	//具有前缀和性质

	int mid = l + r >> 1;
	if (k <= x)return query(tr[p].l, tr[pre].l, l, mid, k);
	return query(tr[p].r, tr[pre].r, mid + 1, r, k - x);
}

void solve() {
	int que;
	cin >> n >> que;
	forr(i, 1, n) {
		cin >> a[i];
		vec.push_back(a[i]);
	}
	sort(all(vec));
	vec.erase(unique(all(vec)), vec.end());
	m = vec.size();

	root[0] = build(1, m);

	for (int i = 1; i <= n; i++)//迭代
		root[i] = update(root[i - 1], 1, m, find(a[i]));

	while (que--)
	{
		int l, r, k;
		cin >> l >> r >> k;
		int t = query(root[r], root[l - 1], 1, m, k);
		cout << vec[t - 1] << endl;
	}
}

signed main() {
	cinios;
	int T = 1;
	forr(t, 1, T) {
		solve();
	}
	return 0;
}
/*
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值