POJ2104 K-th number 函数式线段树
很久没打代码了,不知道为什么,昨天考岭南文化之前突然开始思考起这个问题来,这个问题据说有很多种方法,划分树什么的,不过对于我现在这种水平还是用熟悉的线段树做比较好。这到题今年8月份的时候曾经做过,那个时候是作为对函数式线段树的一个基础题来做的,那个时候不懂,看着别人的代码对拍然后摸索下来,所以到了昨天我就已经彻底忘了,因为代码不是自己写的。昨天想了很久,终于参透了它的精髓。
首先对于给出的a[1]~a[n]这么多个数离散化,然后建立一个线段树,线段树中的结点对应的区间[L,R]表示的是在[L,R](离散化后的值)中有多少个数。
举个例子,a={1,3,2,6,4,7} 那么假如我对这整个数组建线段树,[3,6]的sum应该为2,因为[3,6]里只有3和5在数组里。
函数式的思想是,假如我建出来从a[1]~a[i]对应的线段树T[i],那么当我要去求数组中的某一段[x,y]在[L,R]里有多少个的时候,实际上就是T[y].[L,R]的和-T[x-1].[L,R]的和,这样我们就可以知道,在数组的a[x]~a[y]里在范围在L-R中的数有多少个。这样的话我们可以二分出第kth值。
即从0~size,如果0~mid里已经有超过k个了,说明第k大在左边,否则第k大在右边,这样查找就可以了。这次我终于写出了一个自己的二分。以前二分都是lower_bound水过,如果要用到的话,我会在l+1=r的时候特判,特别猥琐,看了一篇关于二分的总结才知道,二分的写法有分左闭右闭和左开右开的,不过这些都要慢慢领悟了。
再谈谈怎么建线段树,假如我们已经建了线段树T[i],那么当我们要建线段树T[i+1]的时候,如果再建一次空间会非常大,所以要充分利用T[i]的信息,实际上建立T[i+1]的时候只比T[i]多了一个值,所以我们只需要延着路径重建这些新的结点就好,另外一边的只需要用回历史版本的就好。延着线段树往下会修改到logn个结点,所以建立T[0]时需要开4*n,再加上后来每个一个都要logn个结点,所以总的结点数是(4+logn)*n. logn=16.多,这也就是为什么我们的结点数要开到20倍,所以这种树对空间的消耗是蛮大的。
想想之前线段树都要套模板,现在可以自己敲出来,这一年里还是有进步的。我还是慢慢努力练习练习吧~
贴一记代码,1700ms,非常慢,权当学习- -0
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
#include<cstdio>
#include<cmath>
#define maxn 100000
using namespace std; struct Node
{
int l,r,sum;
Node *lc,*rc;
}N[20*maxn];
int top; Node *root[100050];
int n,m;
int a[maxn+20];
int b[maxn+20];
int bsize; Node *build(int L,int R)
{
if(L==R){
Node *ret=&N[top++];
ret->l=L;ret->r=R;ret->sum=0;
ret->lc=NULL;ret->rc=NULL;
return ret;
}
else{
Node* ret=&N[top++];
int M=(L+R)>>1;
ret->l=L;ret->r=R;
ret->lc=build(L,M);ret->rc=build(M+1,R);
ret->sum=ret->lc->sum+ret->rc->sum;
return ret;
}
} Node *build(int L,int R,int k,Node *p) // 对key值k,和历史版本的p重建树
{
if(L==R){
Node *ret=&N[top++];
ret->l=L;ret->r=R;ret->sum=p->sum+1;
ret->lc=NULL;ret->rc=NULL;
return ret;
}
else{
int M=(L+R)>>1;
Node *ret=&N[top++];
ret->l=L;ret->r=R;
if(k<=M){
ret->lc=build(L,M,k,p->lc); // k值在原树的左边的时候要重建左子树
ret->rc=p->rc; // 右子树不变,用历史版本
ret->sum=ret->lc->sum+ret->rc->sum; // 更新和值
return ret;
}
else{
ret->rc=build(M+1,R,k,p->rc); // k值在原树的右边的时候要重建右子树
ret->lc=p->lc; // 左子树不变,用历史版本
ret->sum=ret->lc->sum+ret->rc->sum; // 更新和值
return ret;
}
}
} int query(Node *p,int L,int R) // 对结点p的[L,R]区间求和值
{
if(p->l==L&&p->r==R){
return p->sum;
}
else{
int M=(p->l+p->r)>>1;
if(R<=M) return query(p->lc,L,R);
else if(L>M) return query(p->rc,L,R);
else return query(p->lc,L,M)+query(p->rc,M+1,R);
}
} int search(int x,int y,int k) //找从x到y上的区间第k大
{
int l=0,r=bsize;int mid,num;
while(l<r){
mid=(l+r)>>1;
num=query(root[y],l,mid)-query(root[x-1],l,mid); // 算出<=mid的有多少个,利用区间可以减的性质
if(k<=num) r=mid;
else{
k-=num;l=mid+1;
}
}
return l;
} int main()
{
while(cin>>n>>m)
{
for(int i=0;i<n;++i){
scanf("%d",&a[i]);
b[i]=a[i];
}
//离散化的过程
sort(b,b+n);bsize=unique(b,b+n)-b;
for(int i=0;i<n;++i){
a[i]=lower_bound(b,b+bsize,a[i])-b;
}
top=0;
root[0]=build(0,bsize);
for(int i=1;i<=n;++i){
root[i]=build(0,bsize,a[i-1],root[i-1]); // 根据a[i-1]的值,以及上一个版本的树建立新的子树
}
int li,ri,ki;
for(int i=0;i<m;++i){
scanf("%d%d%d",&li,&ri,&ki);
printf("%d\n",b[search(li,ri,ki)]);
}
}
return 0;
}
POJ2104 K-th number 函数式线段树的更多相关文章
- BZOJ 3123 森林(函数式线段树)
题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=3123 题意: 思路:总的来说,查询区间第K小利用函数式线段树的减法操作.对于两棵树的合并 ...
- 学习笔记--函数式线段树(主席树)(动态维护第K极值(树状数组套主席树))
函数式线段树..资瓷 区间第K极值查询 似乎不过似乎划分树的效率更优于它,但是如果主席树套树状数组后,可以处理动态的第K极值.即资瓷插入删除,划分树则不同- 那么原理也比较易懂: 建造一棵线段树(权值 ...
- POJ- 2104 hdu 2665 (区间第k小 可持久化线段树)
可持久化线段树 也叫函数式线段树也叫主席树,其主要思想是充分利用历史信息,共用空间 http://blog.sina.com.cn/s/blog_4a0c4e5d0101c8fr.html 这个博客总 ...
- BZOJ 3207 花神的嘲讽计划Ⅰ(函数式线段树)
题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=3207 题意:给出一个数列,若干询问.每个询问查询[L,R]区间内是否存在某个长度为K的子 ...
- hdu 5111 树链剖分加函数式线段树
这题说的是给了两棵树,各有100000 个节点,然后Q个操作Q<=50000; 每个操作L1 R1 L2 R2.因为对于每棵树都有一个与本棵树其他点与众不同的值, 最后问 在树上从L1到R1这条 ...
- Codeforces538F A Heap of Heaps(函数式线段树)
题意:给你一个数组a[n],对于数组每次建立一个完全k叉树,对于每个节点,如果父节点的值比这个节点的值大,那么就是一个违规点,统计出1~n-1完全叉树下的违规点的各自的个数. 一个直觉的思想就是暴力, ...
- [Usaco2014 Open Gold ]Cow Optics (树状数组+扫描线/函数式线段树)
这道题一上手就知道怎么做了= = 直接求出原光路和从目标点出发的光路,求这些光路的交点就行了 然后用树状数组+扫描线或函数式线段树就能过了= = 大量的离散+模拟+二分什么的特别恶心,考试的时候是想到 ...
- HDU 1394 Minimum Inversion Number(线段树求最小逆序数对)
HDU 1394 Minimum Inversion Number(线段树求最小逆序数对) ACM 题目地址:HDU 1394 Minimum Inversion Number 题意: 给一个序列由 ...
- BZOJ 3110([Zjoi2013]K大数查询-区间第k大[段修改,在线]-树状数组套函数式线段树)
3110: [Zjoi2013]K大数查询 Time Limit: 20 Sec Memory Limit: 512 MB Submit: 418 Solved: 235 [ Submit][ ...
随机推荐
- 《JavaScript高级程序设计》心得笔记-----第五篇章
第二十二章 1. 安全的检测是使用:Object.prototype.toString.call(value); eg: function isArray(value){ return Object ...
- [Guava源码分析]ImmutableCollection:不可变集合
摘要: 我的技术博客经常被流氓网站恶意爬取转载.请移步原文:http://www.cnblogs.com/hamhog/p/3888557.html,享受整齐的排版.有效的链接.正确的代码缩进.更好的 ...
- Linux platform设备简介
Technorati 标签: Linux platform Linux在2.6内核中,针对一系列设备驱动,提供新的管理框架,成为platform机制,推出的目的,在于隔离驱动的资源和实现,使得 ...
- java基础-浅复制与深复制的理解
浅复制与深复制在很多编程语言中都有出现,那么什么是浅复制,什么是深复制呢? 要区分浅复制与深复制,首先我们要明确什么是复制,怎样才算是复制.复制的例子在生活中也随处可见,如复印一份文档,复制一段文字等 ...
- 村田噪声抑制基础教程-第一章 需要EMI静噪滤波器的原因
1-1. 简介 EMI静噪滤波器 (EMIFIL®) 是为电子设备提供电磁噪声抑制的电子元件,配合屏蔽罩和其他保护装置一起使用.这种滤波器仅从通过连线传导的电流中提取并移除引起电磁噪声的元件.第1章说 ...
- 解決 imagick 在 多线程运行时导致CPU暴增到100%的方法
假如把imagic 安装到 /usr/local/imagemagick 目录 首先用/usr/local/imagemagick/bin/convert -version指令查看一下输出內容是否已经 ...
- matlab之图像处理(2)
diagram = imread('lena1.png') diagram = rgb2gray(diagram);%------------------------------将图片转换为灰度图 N ...
- PHP学习之数组的定义和填充
数组就是把一组数据按顺序放在一起.PHP的数组和其它的语言数组有一点点不同:第一,保存的数据是可以是任何类型的:第二,数组的索引可以是数字,也可以是字符串. PHP的数组,说白了,就是关联数据每一条数 ...
- 应用js改变问章字体大小
刚来公司的时候领导给分配的都是一些简单的简单的简单的.....任务 一次叫我把文章的字体大小变换功能写出来.在网上搜了很多都不管用!不过功夫不负有心人还是被我找到了!拿出来分享下! <scrip ...
- python 安装 setuptools Compression requires the (missing) zlib module 的解决方案
背景: 虚拟机centos下安装python辅助工具 setuptools报错,错误信息大概如下: Traceback (most recent call last): File "setu ...