您的位置:首页 > 其它

【主席树入门 && 区间内第k小的数】POJ - 2104 K-th Number

2017-09-22 11:47 573 查看
Problem Description

输入n, m代表有n个元素,m个访问。接下来输入n个元素。输入m行访问,每行有三个元素l r k。问你下标l - r里面第k小的数是几?

思路:

学长讲堂blue学长很棒的给我们入了个门。自己也能手敲出来这题,也算理解这题了。主席树由n个线段树组成,每个线段树只有logn的节点,只保存存在该元素的节点。

代码:

#include<cstdio>
#include<algorithm>
using namespace std;
struct node
{
int sum, l, r;//该区间有几个数,记录左孩子对应的tree[]下标,记录右孩子对应tree[]下标
};
#define nn 100005
#define MID int mid = (l + r)/2
node tree[nn * 40];//n * logn的节点数 × 40肯定够了。
int a[nn], root[nn], cnt;//a[]存输入元素,root[]存每个线段数的根对应tree[]里的下标,cnt用的结点数
int sca[nn], num;//离散化后的数组,大小
int Creat(int sum, int l, int r)//申请一个新的结点
{
++cnt;
tree[cnt].l = l;
tree[cnt].r = r;
tree[cnt].sum = sum;
return cnt;
}
void Insert(int &root, int pre, int l, int r, int pos)
{
//申请一个结点保存,在前一颗树对应结点基础上copy。sum + 1元素多了一个。
root = Creat(tree[pre].sum + 1, tree[pre].l, tree[pre].r);
if(l == r) return;
MID;
if(pos <= mid)//左边递归
Insert(tree[root].l, tree[pre].l, l, mid, pos);
else Insert(tree[root].r, tree[pre].r, mid + 1, r, pos);
}
int Query(int s, int e, int l, int r, int k)//查找s+1 - e中第k小的数
{
if(l == r) return l;//叶子结点,找到了返回
//左孩子的sum差。
int sum = tree[tree[e].l].sum - tree[tree[s].l].sum;
MID;
int red;
if(k <= sum)//和k判断,如果k<=sum代表 k在左孩子那边
red = Query(tree[s].l, tree[e].l, l, mid, k);
else red = Query(tree[s].r, tree[e].r, mid + 1, r, k - sum);
return red;
}
int main()
{
int n, m, i;
while(~scanf("%d %d", &n, &m))
{
cnt = 0;
root[0] = 0;
for(i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
sca[i] = a[i];
}
sort(sca + 1, sca + n + 1);
num = unique(sca + 1, sca + n + 1) - (sca + 1);//离散化
for(i = 1; i <= n; i++)
{
int x = lower_bound(sca + 1, sca + n + 1, a[i]) - sca;//a[i]离散化后的值
//插入一个元素,相当于在前一棵树的copy上,建一颗新的树
Insert(root[i], root[i - 1], 1, num, x);
}
int l, r, k;
while(m--)
{
scanf("%d %d %d", &l, &r, &k);
printf("%d\n", sca[Query(root[l - 1], root[r], 1, num, k)]);
}
}
}


感觉这玩意注释,不好表达,所以附一份学长的模板代码

// 主席树区间第K大 模板

const int MAXN = 100001;

// 本代码中所有数据均按从下标 1 开始存放

// 主席树中的线段树结点,sum 表示此区间内元素个数
struct node {
int sum, l, r;
} hjt[MAXN*40];

int sorted[MAXN], num;  // sorted: 离散化后的数组 num: 离散化后的数组长度
int root[MAXN], cnt;    // root: 主席树中用来保存每棵线段树树根的数组 cnt: 线段树结点数(用于数组方式动态开点)

// 查找离散化之后的下标
int GetIdx(int v) {
return lower_bound(sorted+1, sorted+1+num, v) - sorted;
}

// 初始化
void Init() {
cnt = 0;
root[0] = 0;
}

// 创建结点
inline int CreateNode(int sum, int l, int r) {
int idx = ++cnt;
hjt[idx].sum = sum;
hjt[idx].l = l;
hjt[idx].r = r;

return idx;
}

// 新建一棵线段树,只沿更新路径新建出较上个版本有修改的结点
// 调用参数
// root: 插入后新生成的线段树的根结点,直接赋值到 root 中
// pre_rt: 上一棵线段树的根
// pos: 本次要插入的值
// l, r: 递归参数。默认填写 1, num
void Insert(int &root, int pre_rt, int pos, int l, int r) {
// 动态创建结点,直接根据上一个版本复制对应的节点,sum+1
root = CreateNode(hjt[pre_rt].sum+1, hjt[pre_rt].l, hjt[pre_rt].r);
if(l == r) return;
int m = (l+r) >> 1;
if(pos <= m)
Insert(hjt[root].l, hjt[pre_rt].l, pos, l, m);
else Insert(hjt[root].r, hjt[pre_rt].r, pos, m+1, r);
}

// 适用于查询区间 [l, r] 中的第 k 小。通常需要自行变通
// 调用参数
// s, e: 要查询区间所需的两个线段树的根,如要查询区间 [l, r],则传入 root[l-1], root[r]
// l, r: 递归参数。默认填写 1, num
// k: 要查询区间第几小
int Query(int s, int e, int l, int r, int k) {
if(l == r) return l;
int m = (l+r) >> 1;
int sum = hjt[hjt[e].l].sum - hjt[hjt[s].l].sum;    // 计算左子树的元素数量
if(k <= sum)    // 如果 k <= sum,则 k 在左子树,否则在右子树
return Query(hjt[s].l, hjt[e].l, l, m, k);
else return Query(hjt[s].r, hjt[e].r, m+1, r, k-sum);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: