K-th Number 线段树(归并树)+二分查找

                            K-th Number

题意:给定一个包含n个不同数的数列a1, a2, ..., an 和m个三元组表示的查询。对于每个查询(i, j, k), 输出ai, ai+1, ... ,aj的升序排列中第k个数 。

题解:用线段树,每个节点维护一个区间并且保证内部升序,对于每次查询x,返回该区间小于x的数的个数。就这样不断二分,直到找到x为止。

线段树(归并树)+二分查找

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <cmath>
 5 #include <algorithm>
 6 #include <string>
 7 #include <vector>
 8 #include <set>
 9 #include <map>
10 #include <stack>
11 #include <queue>
12 #include <sstream>
13 #include <iomanip>
14 using namespace std;
15 typedef long long LL;
16 const int INF=0x4fffffff;
17 const int EXP=1e-5;
18 const int MS=100005;
19
20 struct node
21 {
22       int l,r;
23       vector<int> vec;
24 }nodes[4*MS];
25
26 int a[MS];
27 int num[MS];
28 int n,m;
29
30 void build(int root,int l,int r)
31 {
32       nodes[root].l=l;
33       nodes[root].r=r;
34       nodes[root].vec.clear();
35       if(r-l==1)
36       {
37             nodes[root].vec.push_back(a[l]);
38             return ;
39       }
40       int mid=(l+r-1)>>1;
41       build(root<<1,l,mid+1);
42       build(root<<1|1,mid+1,r);
43       nodes[root].vec.resize(r-l);
44       merge(nodes[root<<1].vec.begin(),nodes[root<<1].vec.end(),
45            nodes[root<<1|1].vec.begin(),nodes[root<<1|1].vec.end(),nodes[root].vec.begin());
46 }
47
48 int query(int root,int l,int r,int x)
49 {
50       if(r<=nodes[root].l||nodes[root].r<=l)
51             return 0;
52       else if(nodes[root].l>=l&&nodes[root].r<=r)
53             return upper_bound(nodes[root].vec.begin(),nodes[root].vec.end(),x)-nodes[root].vec.begin();
54       else
55       {
56             int lcnt=query(root<<1,l,r,x);
57             int rcnt=query(root<<1|1,l,r,x);
58             return lcnt+rcnt;
59       }
60 }
61
62 int main()
63 {
64       while(scanf("%d%d",&n,&m)!=EOF)
65       {
66             for(int i=0;i<n;i++)
67             {
68                   scanf("%d",&a[i]);
69                   num[i]=a[i];
70             }
71             sort(num,num+n);
72             build(1,0,n);
73             int s,t,k;
74             for(int i=0;i<m;i++)
75             {
76                   scanf("%d%d%d",&s,&t,&k);
77                   s--;
78                   int l=-1,r=n-1;
79                   /*  注意  根据问题特点,这里应该是l=-1,r=n-1.
80                         如果情况是询问[l,n]这个区间第n-l+1大的值,并且这个值在最后的位置,那么最后的结果会是num[n],越界
81                         也就是说  二分的结果总是l=mid,知道r-l<=1;         细节问题需要注意
82                   */
83                   while(r-l>1)
84                   {
85                         int mid=(l+r)>>1;
86                         int cnt=query(1,s,t,num[mid]);
87                         if(cnt>=k)
88                               r=mid;
89                         else
90                               l=mid;
91                   }
92                   printf("%d\n",num[r]);
93             }
94       }
95       return 0;
96 }

我写的块状数组超时了。不知道是算法真的超时,还是细节问题导致超时。日后再来改正。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <string>
#include <vector>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <sstream>
#include <iomanip>
using namespace std;
typedef long long LL;
const int INF=0x4fffffff;
const int EXP=1e-5;
const int MS=100005;
const int SIZE=1000;

int n,m;
int a[MS];
int order[MS];

vector<int> bucket[MS/SIZE];

int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        for(int i=0;i<MS/SIZE;i++)
            bucket[i].clear();             // 千万注意清空
        for(int i=0;i<n;i++)
        {
            scanf("%d",&a[i]);
            bucket[i/SIZE].push_back(a[i]);
            order[i]=a[i];
        }
        sort(order,order+n);
        for(int i=0;i<n/SIZE;i++)
            sort(bucket[i].begin(),bucket[i].end());
        int s,t,k;
        while(m--)
        {
            scanf("%d%d%d",&s,&t,&k);
            s--;                         //[s,t)        二分查找使用左必有开更方便一些
            int l=-1,r=n-1;      //   注意:  根据问题的性质,l=0,r=n是错误的,因为有情况总是mid=l,一直到到
                                          //   n-l<=1,  这时答案是num[n],不在给定的数组范围内了。
            while(r-l>1)
            {
                int mid=(l+r)>>1;
                int x=order[mid];
                int tl=s,tr=t,c=0;
                //  处理区间两端多出的部分
                while(tl<tr&&tl%SIZE!=0)
                    if(a[tl++]<=x)
                        c++;
                while(tl<tr&&tr%SIZE!=0)   //  左闭右开  处理方便一些
                    if(a[--tr]<=x)
                        c++;
                // 对每一个桶进行统计
                while(tl<tr)
                {
                    int id=tl/SIZE;
                    c+=upper_bound(bucket[id].begin(),bucket[id].end(),x)-bucket[id].begin();
                    tl+=SIZE;
                }
                if(c>=k)
                    r=mid;
                else
                    l=mid;
            }
            printf("%d\n",order[r]);
        }
    }
    return 0;
}
时间: 2025-01-15 14:32:27

K-th Number 线段树(归并树)+二分查找的相关文章

POJ 2182 Lost Cows (树状数组 &amp;&amp; 二分查找)

题意:给出数n, 代表有多少头牛, 这些牛的编号为1~n, 再给出含有n-1个数的序列, 每个序列的数 ai 代表前面还有多少头比 ai 编号要小的牛, 叫你根据上述信息还原出原始的牛的编号序列 分析:如果倒着看这个序列的话, 那序列的最后一个元素就能够确定一个编号.举个例子:如果序列的最后一个元素为0, 那就说明这头牛前面再也没有比它编号更小的牛了, 所以这头牛的编号肯定是最大的, 我们只要给它所在的编号加个标记, 然后继续根据倒数第二个.第三个--来依次确定便可还原整个序列, 这里可以使用树

leetcode-374-Guess Number Higher or Lower(二分查找)

题目描述: We are playing the Guess Game. The game is as follows: I pick a number from 1 to n. You have to guess which number I picked. Every time you guess wrong, I'll tell you whether the number is higher or lower. You call a pre-defined API guess(int n

(经典) K&amp;R的名著&lt;&lt;C程序设计语言&gt;&gt;二分查找

#include<stdio.h> //查找成功则返回所在下标否则返回-1 int binsearch(int A[], int n,int a) { int low, high, mid; low = 0; high = n -1; while ( low <= high) { /// 这里必须是 <= mid = (low+high) / 2; if (A[mid] == a) return mid; else if(A[mid]<a) low = mid +1; els

离散化+线段树/二分查找/尺取法 HDOJ 4325 Flowers

题目传送门 题意:给出一些花开花落的时间,问某个时间花开的有几朵 分析:这题有好几种做法,正解应该是离散化坐标后用线段树成端更新和单点询问.还有排序后二分查找询问点之前总花开数和总花凋谢数,作差是当前花开的数量,放张图易理解: 还有一种做法用尺取法的思想,对暴力方法优化,对询问点排序后再扫描一遍,花开+1,花谢-1.详细看代码. 收获:一题收获很多:1. 降低复杂度可以用二分 2. 线段计数问题可以在端点标记1和-1 3. 离散化+线段树 终于会了:) (听说数据很水?) 代码1:离散化+线段树

HDU 2852 KiKi&#39;s K-Number (树状数组 &amp;&amp; 二分)

题意:给出对容器的总操作次数n, 接下来是这n个操作.这里对于一个容器提供三种操作, 分别是插入.删除和查找.输入0  e表示插入e.输入1  e表示删除e,若元素不存在输出No Elment!.输入2  e  k表示查找比e大且第k大的数, 若不存在则输出Not Find! 分析:这里考虑树状数组做的原因是在第三个操作的时候, 只要我们记录了元素的总数, 那通过求和操作, 便能够高效地知道到底有多少个数比现在求和的这个数要大, 例如 tot - sum(3)就能知道整个集合里面比3大的数到底有

数据结构之二分查找树总结

说明:本文仅供学习交流,转载请标明出处,欢迎转载! 二分查找树BST(也叫二叉查找树.二叉排序树)的提出是为了提供查找效率,之所以称为二分查找树,因为该二叉树对应着二分查找算法,查找平均的时间复杂度为o(logn),所以该数据结构的提出是为了提高查找效率. 定义 二分查找树或者是一棵空树,或者具有下列性质: 1.若它的左子树不为空,则左子树上所有结点的值均小于根结点的值: 2.若它的右子树不为空,则右子树上所有结点的值均大于根结点的值: 3.它的左右子树均为二分查找树. 操作 二分查找树的操作主

二分查找判定树

5.二分查找判定树 二分查找过程可用二叉树来描述:把当前查找区间的中间位置上的结点作为根,左子表和右子表中的结点分别作为根的左子树和右子树.由此得到的二叉树,称为描述二分查找的判定树(Decision Tree)或比较树(Comparison Tree). 注意: 判定树的形态只与表结点个数n相关,而与输入实例中R[1..n].keys的取值无关. [例]具有11个结点的有序表可用下图所示的判定树来表示. (1)二分查找判定树的组成 ①圆结点即树中的内部结点.树中圆结点内的数字表示该结点在有序表

查找算法(I) 顺序查找 二分查找 索引查找

查找 本文为查找算法的第一部分内容,包括了基本概念,顺序查找.二分查找和索引查找.关于散列表和B树查找的内容,待有空更新吧. 基本概念 查找(search)又称检索,在计算机上对数据表进行查找,就是根据所给条件查找出满足条件的第一条记录(元素)或全部记录. 若没有找到满足条件的记录,则返回特定值,表明查找失败:若查找到满足条件的 第一条记录,则表明查找成功,通常要求返回该记录的存储位置或记录值本身,以便进行进一步处理:若需要查找到满足条件的所有记录,则可看做在多个区间内连 续查找到满足条件的第一

转:线性表的查找-二分查找

转自:http://student.zjzk.cn/course_ware/data_structure/web/chazhao/chazhao9.2.2.1.htm 二分查找 1.二分查找(Binary Search)     二分查找又称折半查找,它是一种效率较高的查找方法.    二分查找要求:线性表是有序表,即表中结点按关键字有序,并且要用向量作为表的存储结构.不妨设有序表是递增有序的. 2.二分查找的基本思想    二分查找的基本思想是:(设R[low..high]是当前的查找区间)