主席树 POJ 2104

关于主席树,其实就是线段树的变形。只不过主席树,每个节点,例如 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;
}

猜你喜欢

转载自blog.csdn.net/weixin_44019404/article/details/88749237