静态主席树

【转】主席树:对于序列的每一个前缀建一棵以序列里的值为下标的线段树(所以要先离散化),记录该前缀序列里出现的值的次数;记离散后的标记为1~n; (下面值直接用1~n代替;)

对于区间[x,y]的第k大的值,那么从root[x-1],root[y]开始,t=root[y].[1,mid]-root[x-1].[1,mid] ,t表示区间[x,y]内值在[1,mid]的个数 先判断t是否大于K。

如果t大于k,那么说明在区间[x,y]内存在[1,mid]的数的个数大于k,也就是第k大的值在[1,mid]中,否则在[mid+1,r]中;

这样我们依次同时从root[x-1],root[y]往下走当l==r时 第k大的值就是离散后标记为l的值

;如果每棵线段都建完整的化,n*(n<<2)肯定会mle,我们发现对于前缀[1,i]和前缀[1,i+1]的线段树,如果b[i+1]<=mid (b[i+1]表示a[i+1]离散后的标记)那么线段树i和线段树i+1的左边是完全相同的,根本不需要再建,只需要用指针指一下就好;那么对于一棵新的线段树其实我们最多要建的节点数为log(n);nlog(n)的节点数还是可以忍受的;

 1 #include <cstdio>
 2 #include <iostream>
 3 #include <algorithm>
 4 #include <cstring>
 5 #include <cmath>
 6 using namespace std;
 7 const int maxn = 10000 + 10;
 8 const int maxnn = 100 * maxn;
 9 int n, Q, ms, tot;
10 int root[maxn], A[maxn], ls[maxnn], rs[maxnn], s[maxnn];
11 int num[maxn], hash[maxn];
12 int find(int x){
13     int L = 1, R = tot, M;
14     while(L <= R){
15         M = L + R >> 1;
16         if(hash[M] < x) L = M + 1;
17         else R = M - 1;
18     }
19     return L;
20 }
21 void build(int x, int& y, int L, int R, int v){
22     s[y = ++ ms] = s[x] + 1;
23     if(L == R) return;
24     ls[y] = ls[x]; rs[y] = rs[x];
25     int M = L + R >> 1;
26     if(v <= M) build(ls[x], ls[y], L, M, v);
27     else build(rs[x], rs[y], M + 1, R, v);
28     return ;
29 }
30 int query(int x, int y, int L, int R, int k){
31     if(L == R) return L;
32     int M = L + R >> 1, kth = s[ls[y]] - s[ls[x]];
33     if(kth >= k) return query(ls[x], ls[y], L, M, k);
34     else return query(rs[x], rs[y], M + 1, R, k - kth);
35 }
36 void read(int &x){
37     x = 0; int sig = 1; char ch = getchar();
38     while(!isdigit(ch)) { if(ch == ‘-‘) sig = -1; ch = getchar(); }
39     while(isdigit(ch)) x = 10 * x + ch - ‘0‘, ch = getchar();
40     x *= sig; return ;
41 }
42 void init(){
43     read(n); read(Q);
44     for(int i = 1; i <= n; i ++) read(A[i]), num[i] = A[i];
45     sort(num + 1, num + n + 1); hash[++ tot] = num[1];
46     for(int i = 2; i <= n; i ++) if(num[i] != num[i - 1]) hash[++ tot] = num[i];
47     for(int i = 1; i <= n; i ++) build(root[i - 1], root[i], 1, tot, find(A[i]));
48     return ;
49 }
50 void work(){
51     int _i, _j, _k;
52     while(Q --){
53         read(_i); read(_j); read(_k);
54         printf("%d\n", hash[query(root[_i - 1], root[_j], 1, tot, _k)]);
55     }
56     return ;
57 }
58 void print(){
59
60     return ;
61 }
62 int main(){
63     init();
64     work();
65     print();
66     return 0;
67 }
时间: 2024-10-24 04:27:06

静态主席树的相关文章

ZOJ 2112 Dynamic Rankings(主席树套树状数组+静态主席树)

题意:给定一个区间,求这个区间第k大的数,支持单点修改. 思路:主席树真是个神奇的东西.........速度很快但是也有一个问题就是占用内存的很大,一般来说支持单点修改的主席树套树状数组空间复杂度为O(n*logn*logn), 如果查询较少的话,可以初始的时候用一颗静态主席树,这样空间复杂度可以降为O(n*logn+q*logn*logn),勉强可以过zoj这道题. 这道题看了好久好久才懂...网上题解一般就几句话.......下面是自己的一些理解. 首先静态主席树这个东西其实比较好懂,就是对

静态主席树总结(静态区间的k大)

静态主席树总结(静态区间的k大) 首先我们先来看一道题 给定N个正整数构成的序列,将对于指定的闭区间查询其区间内的第K小值. 输入格式: 第一行包含两个正整数N.M,分别表示序列的长度和查询的个数. 第二行包含N个正整数,表示这个序列各项的数字. 接下来M行每行包含三个整数 l, r, kl,r,k , 表示查询区间[l, r][l,r] 内的第k小值. 输出格式: 输出包含k行,每行1个正整数,依次表示每一次查询的结果 对于100%的数据满足:\(1 \leq N, M \leq 2\cdot

poj 2104 静态主席树

我的第一道主席树(静态). 先记下自己对主席树的理解: 主席树的作用是用于查询区间第k大的元素(初始化nlog(n),查询log(n)) 主席树=可持续线段树+前缀和思想 主席树实际上是n棵线段树(由于是可持续化线段树,所以实际上是n个长度为log(n)的链),第i棵线段树保存的是a[1]~a[i]这i个数的值域线段树,每个节点维护的是该值域中元素个数,这个是可以相减的,所以建完树后,查询[lf,rg]的第k大时,保存当前查询的值域区间在lf-1和rg这两棵线段树中的节点u,v(不理解看代码),

poj2104求区间第k小,静态主席树入门模板

看了很久的主席树,最后看https://blog.csdn.net/williamsun0122/article/details/77871278这篇终于看懂了 #include <stdio.h> #include<algorithm> using namespace std; typedef long long ll; const int maxn = 1e5+5; int T[maxn],L[maxn*20],R[maxn*20],sum[maxn*20]; //sz[]为原

POJ 2104 K-th Number 静态主席树(裸

题目链接:点击打开链接 题意: 给定n长的序列,q个询问 下面n个数字给出序列 每个询问[l, r] k ,输出该区间中第k大的数 先建一个n个节点的空树,然后每次从后往前新建一棵树,依附原来的空树建.询问就是在树上二分. #include <stdio.h> #include <stdlib.h> #include <algorithm> #include <vector> #include <iostream> #include <cs

【静态主席树】POJ2104-K-th Number

求区间第k大.裸线段树. 莫队版本:☆ 1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<vector> 6 #define lson l,m 7 #define rson m+1,r 8 using namespace std; 9 const int MAXN=100000+50; 10 int sum[MA

[静态主席树]Couriers

题目描述 给一个长度为n的序列a.1≤a[i]≤n.m组询问,每次询问一个区间[l,r],是否存在一个数在[l,r]中出现的次数大于(r-l+1)/2.如果存在,输出这个数,否则输出0. 输入 第一行两个数n,m(n,m≤500000).第二行n个数,a[i].接下来m行,每行两个数l,r,表示询问[l,r]这个区间. 输出 m行,每行对应一个答案. 样例输入 7 5 1 1 3 2 3 4 3 1 3 1 4 3 7 1 7 6 6 样例输出 1 0 3 0 4 思路:建立可持久化权值线段树A

【模板】静态主席树

如题,这是一个模板... 1 #include <algorithm> 2 #include <iostream> 3 #include <cstring> 4 #include <cstdio> 5 #include <cctype> 6 #include <cmath> 7 8 inline void read(int & x) 9 { 10 x = 0; 11 int k = 1; 12 char c = getchar

[主席树]ZOJ2112 Dynamic Rankings

题意:n个数,q个询问 (n<=50000, q<=10000) Q x y z 代表询问[x, y]区间里的第z小的数 C x y    代表将(从左往右数)第x个数变成y 上篇介绍了在[x, y]区间内查询第z小的数的方法(静态主席树) 本题有更新操作 若仍用上篇的做法, 每次更新一个数,需要更新的是T[i], T[i+1]... ...T[n](该数所在的树以及它后面的所有树) 因为每棵树T[i]所记录的都是前缀(1到i的数出现的次数) 因此,改变i,会影响i到n的所有树 这样,每次更新