hdu2665区间第K大+主席树解题报告

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_33468963/article/details/77487754

链接:http://acm.hdu.edu.cn/showproblem.php?pid=2665

多组数据的区间第k大 n<10^5

0x00:构造权值线段树
和普通线段树相似,每次插入 a[i] 时对下标为 a[i] 的位置加一,
这样做就可以用 O(log n) 的时间查询全局第k大了(但有可能要离散化)

0x01:可持续化线段树
可持续化线段树记录每一颗线段树的根,以保留所有的历史版本
除了第一次构造出整颗线段树之外,之后每一次只用将有改变的节点构造出来
其他节点均连接到上一个历史版本的相同位置

if (k <= mid)
add(tr[now].ls = _new(),tr[old].ls,k,l,mid), tr[now].rs = tr[old].rs;
// now -> 现在正在处理的位置  old -> 历史版本中的位置

0x02:构建主席树
主席树就是一颗可持续化线段树。
每插入一个 a[i] 都新建一个历史版本,时空复杂度都是 O(log n)
有些写法里要先构建一颗线段树,再在其上面增加历史版本
但其实是没有必要的(只针对此题,特殊情况另外讨论)
因为 tr[0] 的 左右儿子节点都是 0,tr[0] 可以说就是一颗无穷大的线段树

0x03:线段树相减
对于线段树的每一个节点,都记录着该点所对应区间有多少个 a[i]
因此,tree[r] - tree[l-1] 就可以得到对应区间有多少个 a[i] 在区间 [l,r] 中
即可查询区间第k大

0x04:代码实现:
这道题有多组数据
所以

一定要清空!!!

(hdu上看不到输入数据所以调了很久)

*主席树↓

#include <cstdio>
#include <queue>
#define N 200100
#define mid ((l+r)>>1)
using namespace std;

int T,n,m,i,rt[N],tot,ans[N],a[N],rnk[N];
struct point {int num,ls,rs;} tr[N << 4];
priority_queue<pair<int,int> > q;

inline int _new() {tr[++tot].num = 0;  return tot;}
void add(int now,int old,int k,int l,int r) {
    if (l == r) return void(++tr[now].num);
    if (k <= mid) add(tr[now].ls = _new(),tr[old].ls,k,l,mid), tr[now].rs = tr[old].rs;
    if (k > mid) add(tr[now].rs = _new(),tr[old].rs,k,mid+1,r), tr[now].ls = tr[old].ls;
    tr[now].num = tr[tr[now].ls].num + tr[tr[now].rs].num;
}
int ask(int nowl,int nowr,int k,int l,int r) {
    if (l == r) return l;
    if (tr[tr[nowr].ls].num - tr[tr[nowl].ls].num >= k) return ask(tr[nowl].ls,tr[nowr].ls,k,l,mid);
    return ask(tr[nowl].rs,tr[nowr].rs,k - (tr[tr[nowr].ls].num - tr[tr[nowl].ls].num),mid+1,r);
}

int main() {
    for (scanf("%d",&T);T--;tot = 0) {
        scanf("%d%d",&n,&m);
        for (i=1;i<=n;i++) {
            scanf("%d",a+i);
            q.push(make_pair(-a[i],i));
        }
        for (i=1;i<=n;i++) {
            pair<int,int> tmp = q.top();  q.pop();
            ans[rnk[tmp.second] = i] = -tmp.first;
        }
        for (i=1;i<=n;i++) add(rt[i] = _new(),rt[i-1],rnk[i],1,n);
        for (int l,r,k;m--;) {
            scanf("%d%d%d",&l,&r,&k);
            printf("%d\n",ans[ask(rt[l-1],rt[r],k,1,n)]);
        }
    }
}

关于主席树的由来:
据说该算法的发明者是 HJT

猜你喜欢

转载自blog.csdn.net/qq_33468963/article/details/77487754
今日推荐