用树解决K-th Number之主席树

K-th Number

1.主席树解法

前方高能,你需要(一些线段树姿势)

答记者问

Q:为什么叫主席树?叫主席树会不会给人一种钦定的感觉?
A:因为某大佬考场上忘记怎么写划分树了,于是当场yy出来了,叫主席树跟树本身特征没关系(至少我这么理解),是因为那个大佬叫hjt,我说的意思你懂吧。


Q:什么是主席树?
A:我分析算法都喜欢按照基本法,从暴力的来,比如哦,要在线询问前 k次修改后的线段树()的内容怎么办办啊。
(凤凰台记者)我知道,把修改过的全记下来啊。
唉,西方的那个暴力我没有见过,不知道比你们高到哪里去了。主席树就是暴力啊,只不过你看看是不是每次修改只有从根到该节点的值改了,我只要新建这些点就行呢你们啊,naive,会有K棵树,MLE了,还是O(Kn)的,主席树只要O(nlogn),不超时,不超空间(nlogn 习惯性线段树*4 主席树*40),中央是资磁的。


Q:这题怎么用呢?
A:就把N长的数列分成N个前缀,记录到N棵线段树里面,其实就相当于上一棵前缀树(root[N-1])+这个数(A[N])组成的新树=这棵前缀树(root[N])(离散化是必须的!),求区间第k大就,比较头(l-1)尾(r)两个线段树(对,其实就是两棵完完整整的线段树),然后你可以比较它们之间有什么差别=-=!!!(真实的故事)数量差别(T[x] - T[Y])就是之间的数(你要求第k大的区间的数量)产生的,所以你比较,如果k小于等于左子树的差,说明第k大在左边,向左走,不然向右并且减掉左子树的差)。


Q:为什么是比较两树值差呢?
A:差代表了这段区间啊,西方的哪个前缀不是这么玩的,不知道妙到哪里去了,差其实是为这个区间再建的一棵前缀树的值
线段树(root[l->r])=前缀树(root[r])-前缀树(root[l-1])


Q:(naive记者)那么,先生那有没有其他想法呢?
A:当然是划分树和整体二分啦,什么时候贴划分树和整体二分代码要看中央的决定。


先自己试试?

poj2104

Sample Input
7 3
1 5 2 6 3 7 4
2 5 3
4 4 1
1 7 3

Sample Output
5
6
3

我这么说你们可能不懂,看代码!!!

总结:

1.离散化 (排序O(nlogn) 其他O(nlogn)用stl太玄学了,超时不存在的)
2.前缀树 (O(nlogn))
3.在l~r树上查找第k大的树 (前缀树->l~r树 O(1),查找O(logn))

下发文件

#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<algorithm>
#include<vector>
#define N 100010
using namespace std;
vector <int> v;     //步骤1           
int n, m, a[N], root[N], cur;

struct node{        //步骤2
    int l, r, sum;  
}T[N*40];           //步骤2

int getid(int x){return lower_bound(v.begin(),v.end(),x) - v.begin() + 1;}  //步骤1
void build(int l, int r, int& x, int y, int pos)//步骤2
{ 
    T[++cur] = T[y]; x = cur; T[x].sum++;
    if(l == r) return ;
    int mid;
    mid = (l+r) >> 1;
    if(mid >= pos)// 我在这错过,大家小心
        build(l, mid, T[x].l, T[y].l, pos);
    else
        build(mid+1, r, T[x].r, T[y].r, pos);
    return;
}                                       //步骤2

int query(int l ,int r, int x, int y,int k)//步骤3
{
    if(l == r)return l;
    int sum, mid;
    mid = (l+r)>>1;
    sum = T[T[y].l].sum - T[T[x].l].sum;
    if(sum >= k)//小心,可能跟你习惯不一样
        query(l, mid, T[x].l, T[y].l, k);
        else
        query(mid+1, r, T[x].r, T[y].r, k-sum);
}           //步骤3
void init()//init
{
    scanf("%d%d",&n,&m);
    for(int i = 1; i <= n; i++) scanf("%d",&a[i]), v.push_back(a[i]);//步骤1
    sort(v.begin(), v.end());//步骤1 v.erase(unique(v.begin(),v.end()),v.end());//步骤1
    for(int i = 1; i <= n; i++) build(1, n, root[i], root[i-1], getid(a[i]));//步骤2
    return ;    
}
void work()
{   int x, y, k;
    for(int i = 1; i <= m; i++) scanf("%d%d%d",&x,&y,&k),printf("%d\n",v[query(1,n,root[x-1],root[y],k)-1]);//步骤3
    return ;
}
int main()
{
    init();
    work();
    return 0;
}

人生经验

如果你解决本题有什么人生经验(程序错误,我的错误,不懂的地方,奇奇怪怪的优化想法)可以留言偶!,我会选一些挂上。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值