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