关于主席树,其实就是线段树的变形。只不过主席树,每个节点,例如 1-2区间这个节点,他存入的是1-2区间现在有几个数字落在这个区间了,即1-2区间内数字个数。
这里1-2指的是离散化后的大小。
比如 7 8 5 3 2 离散化后,就是4 5 3 2 1
所以1-2区间一直要等到原数组7 8 5 3 2读入到3开始,才会有值,之前一直为零。
所以我们就找到一颗这样的线段树,来储存区间内数字的个数。如果我们要在l-r区间寻找第k个
那么就可以用相减来达到目的。
这里我们只说了一颗线段树,但是实际问题是要输入很多值,所以我们需要将数与树连接在一起。
主席树最难理解的就是如何储存,每棵树之间是如何连接在一起的。
详细看代码注释。
比起树我更愿意叫他主席链表。
结构体里面每一个都要清零!根节点一定来自于上一个。要注意离散化!和左右区间。
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<climits>
#include<stack>
#include<vector>
#include<queue>
#include<set>
#include<map>
#define up(i,a,b) for(int i=a;i<b;i++)
#define dw(i,a,b) for(int i=a;i>b;i--)
#define upd(i,a,b) for(int i=a;i<=b;i++)
#define dwd(i,a,b) for(int i=a;i>=b;i--)
//#define local
typedef long long ll;
const double esp = 1e-6;
const double pi = acos(-1.0);
const long long INF = 0x3f3f3f3f;
using namespace std;
typedef pair<int, int> pir;
int n, m;
struct anw {
int val, i;
}ans[100005];//输入的数据数组
struct tr
{
int l, r, sum;
tr()
{
sum = 0;//零每一个节点的sum先都为零
}
}tree[100005*20];//开大一点
int cnt;
int root[100005];
int sot[100005];
bool cmp(anw a, anw b)
{
return a.val < b.val;//排序
}
void init()//初始化,实际就是作为模板的意思,右子树先都不指向任何节点。cnt为1,从1开始模板才有用。
{
cnt = 1;//cnt表示已经做到了cnt个节点了
root[0] = 0;//root也需要一个模板
tree[0].l = tree[0].r = tree[0].sum = 0;
}
void update(int &pre,int l,int r,int k)//pre可以类比为上个节点的指针域,要加上&才行。
{
tree[cnt] = tree[pre];//每一次令新的节点和传入的节点一摸一样。
//可以想象,如果pre节点左节点或者右节点没有更新过,当前就是一个模板节点,如果有,
//就相当于自己继续用,可以节约空间,这就是主席树的如果将树连在一起的奥秘
pre = cnt;//令pre上个节点指向这个节点
cnt++;
tree[pre].sum++;//当前节点加1
if (l == r)return;//叶子就返回了,当前节点已经加了1,不用继续遍历
int mid = (l + r) >> 1;
if (k <= mid)update(tree[pre].l, l, mid, k);//如果是在左边,把当前节点的做指针传入。
else update(tree[pre].r, mid + 1, r, k);
}
void searchfor(int l, int r, int k,int a,int b)//查询函数
{
int mid = (l + r) >> 1;
int sum = tree[tree[b].l].sum - tree[tree[a].l].sum;//看看两个左边的sum相减为多大
if (l == r) {
cout << ans[l].val << endl; return;
}
if (sum >=k)searchfor(l, mid, k,tree[a].l,tree[b].l);
else searchfor(mid + 1, r, k-sum,tree[a].r,tree[b].r);//这里要注意,左边已经有sum个比你小的数字了
//要查询第k大的数字,那么对于右边来说,就是第k-sum大的数字了。
}
int main()
{
scanf("%d %d", &n, &m);
upd(i, 1, n)
{
scanf("%d", &ans[i].val);
ans[i].i = i;
}
sort(ans+1, ans + n+1,cmp);//排序方便离散
upd(i, 1, n)
{
sot[ans[i].i] = i;//开始离散,注意都要从1开始。
}
init();
upd(i, 1, n)
{
root[i] = root[i - 1];//根节点继承上一个。相当于继承了上一个的左右指针指向的左子树和右子树
update(root[i], 1, n, sot[i]);
}
int x, y, z;
up(i, 0, m)
{
scanf("%d %d %d", &x, &y, &z);
searchfor(1,n,z,root[x-1], root[y]);//要注意有x-1
}
return 0;
}