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
",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
",order[r]);
        }
    }
    return 0;
}

 

原文地址:https://www.cnblogs.com/767355675hutaishi/p/4391216.html