一、题目描述:

  给你一个长度为 $n$ 的序列 $a$ , 你需要回答 $q$ 次询问。

    $求区间\ l\ 到\ r\ 的第\ k\ 小值$

  数据范围:$1 \le n,q \le 5 \times 10^5 ,所有数\ -10^9 \le val \le 10^9$


 二、解题思路:

  首先数据离散化,有效降低时间复杂度。

  明显是一个主席树板子题,但是今天学的是 $整体二分$ 。

  所以我们用 $整体二分$ 来做这个题,时间复杂度 $O(nlog_2^{n^2})$ 。SPOJ 略微卡常。


 三、完整代码:

 1 #include<map>
 2 #include<iostream>
 3 #include<algorithm>
 4 #define N 100010
 5 using namespace std;
 6 map <int,int> rak;
 7 int n,m,tot;
 8 int t[N],a[N],sa[N],ans[N];
 9 struct Query{
10     int l,r,k,id;
11 }q[N*2],q1[N*2],q2[N*2];
12 int ask(int x)
13 {
14     int res=0;while(x)
15         res+=t[x],x-=x&(-x);
16     return res;
17 }
18 void upt(int x,int k)
19 {
20     while(x<=n)
21         t[x]+=k,x+=x&(-x);
22 }
23 //基本树状数组操作 
24 //solve(l,r,L,R)表示询问q[L]到q[R]的答案在l到r之间 
25 void solve(int l,int r,int L,int R)
26 {
27     if(l>r||L>R)    return ;
28     if(l==r)
29     {
30         for(int i=L;i<=R;i++)
31             ans[q[i].id]=l;
32         return ;
33     }
34     int cnt1=0,cnt2=0,mid=(l+r)>>1;
35     //注意到数组总是在前面,询问总是在后面,不用担心 
36     for(int i=L;i<=R;i++)
37     {
38         if(!q[i].id){
39             if(q[i].l>mid) q2[++cnt2]=q[i];
40             else upt(q[i].r,1),q1[++cnt1]=q[i];
41         }else{
42             int tmp=ask(q[i].r)-ask(q[i].l-1);
43             if(q[i].k<=tmp)    q1[++cnt1]=q[i];
44             else q[i].k-=tmp,q2[++cnt2]=q[i];
45             //这里细节比较多,要细细的想一下 
46         }
47     }
48     for(int i=1;i<=cnt1;i++)
49         if(!q1[i].id)
50             upt(q1[i].r,-1);
51     //高效清空树状数组 
52     for(int i=1;i<=cnt1;i++)
53         q[L+i-1]=q1[i];
54     for(int i=1;i<=cnt2;i++)
55         q[L+i+cnt1-1]=q2[i];
56     //这是节约空间的好办法,大概可以应用到 FFT 中 
57     solve(l,mid,L,L+cnt1-1);
58     solve(mid+1,r,L+cnt1,R);
59     //分治,注意范围 
60 }
61 int main()
62 {
63     ios::sync_with_stdio(false);
64     cin.tie(0);cout.tie(0);
65     cin>>n>>m;
66     for(int i=1;i<=n;i++)
67         cin>>q[i].l,q[i].r=i,a[i]=q[i].l;
68     sort(a+1,a+1+n);
69     for(int i=1;i<=n;i++)
70         if(i==1||a[i]!=a[i-1])
71             rak[a[i]]=++tot,sa[tot]=a[i];
72     for(int i=1;i<=n;i++)
73         q[i].l=rak[q[i].l];
74     //离散化 
75     for(int i=1,j=i+n;i<=m;i++,j++)
76         cin>>q[j].l>>q[j].r>>q[j].k,q[j].id=i;
77     solve(1,tot,1,n+m);
78     for(int i=1;i<=m;i++)
79         cout<<sa[ans[i]]<<'\n';
80     //注意答案还原 
81     return 0;
82 }

四、写题心得:

  嗨哟,终于把 $整体二分$ 搞懂了,收获经验如下:

  $1、递归分治中通过重新赋值节约空间=> Exp++!$

  $2、树状数组好板子,直接背就行=> Exp++$

posted on 2023-06-19 22:01  trh0630  阅读(34)  评论(0)    收藏  举报