Description
That is, given an array a[1…n] of different integer numbers, your program must answer a series of questions Q(i, j, k) in the form: “What would be the k-th number in a[i…j] segment, if this segment was sorted?”
For example, consider the array a = (1, 5, 2, 6, 3, 7, 4). Let the question be Q(2, 5, 3). The segment a[2…5] is (5, 2, 6, 3). If we sort this segment, we get (2, 3, 5, 6), the third number is 5, and therefore the answer to the question is 5.
Input
The second line contains n different integer numbers not exceeding 10 9 by their absolute values — the array for which the answers should be given.
The following m lines contain question descriptions, each description consists of three numbers: i, j, and k (1 <= i <= j <= n, 1 <= k <= j - i + 1) and represents the question Q(i, j, k).
Output
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
[Submit] [Go Back] [Status] [Discuss]
分析:
整体二分的例题
在这里再说一下整体二分的实现:
- 确定一个区间以及这个区间对应的答案范围
- 确定当前的判断标准M=(L+R)>>1
- 将整个序列中的操作分成两个序列:q1—>[L,M],q2—>[M+1,R]
- 继续二分
我们从代码中看看算法的实现:
操作分类
我们再读入的时候把操作分成两类:
两种讯问中各个变量的含义是不一样的
一 . 修改:
for (int i=1;i<=n;i++)
{
scanf("%d",&x);
tot++;
q[tot].x=x; q[tot].type=1; q[tot].id=i;
}
//x:数值 type:操作类型 id:在数组中的位置
二 . 询问:
for (int i=1;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&k);
tot++;
q[tot].x=x; q[tot].y=y; q[tot].k=k; q[tot].type=2; q[tot].id=i;
}
//x:询问左端点 y:询问右端点 k:查找第k小 type:操作类型 id:询问编号
整体二分
solve(1,tot,-INF,INF);
//序列左端点 序列右端点 二分的答案左端点 二分的答案右端点
这个所谓的二分答案左右端点,实际上是我们确定的一个范围:序列[ql,qr]这个区间的答案一定位于[L,R]这个数值区间内
First
我们先看极限情况
if (ql>qr) return; //一定要加,避免死循环
if (L==R)
{
for (int i=ql;i<=qr;i++)
if (q[i].type==2) ans[q[i].id]=L;
//记录答案
return;
}
如果我们二分的答案已经唯一了(L==R),那么序列[ql,qr]所有询问的答案就是L
Second
之后我们确定了一下此次判断的标准:int M=(L+R)>>1;
(即这个区间中有哪些询问的答案可能是M)
我们扫一下序列[ql,qr]中所有的操作,按照操作类型进行不同的处理,分入两个不同序列q1,q2中(两个序列对应的答案分别是[L,M],[M+1,R]):
if (q[i].type==1)
{
if (q[i].x<=M) {
add(q[i].id,1);
q1[++t1]=q[i];
}
else q2[++t2]=q[i];
}
如果是修改操作,我们判断一下当前的数值和M的关系:
- x<=M
此问题要求的是区间第k小的数值,所以这种情况下x会给排名产生贡献,我们就在x的位置id上标记1,同时归入第一个序列q1
(实际上这也是一种变向的“左区间修改,右区间询问”) - x>M
这种情况下x不会给排名产生贡献,我们直接归入第二个序列q2
int tt=ask(q[i].y)-ask(q[i].x-1);
if (tt>=q[i].k) q1[++t1]=q[i];
else{
q[i].k-=tt;
q2[++t2]=q[i];
}
如果是询问操作,我们看一下这个区间的答案中能不能是M
也就是说我们看一看当前的区间中有多少小于M的数值
按照这个标准,我们把操作归入两个序列
Third
我们把q1,q2的信息复制到q中,方便之后的二分
我们还要把之前的标记清除(之前我们只有q1中的修改操作进行了标记)
for (int i=1;i<=t1;i++)
if (q1[i].type==1) add(q1[i].id,-1);
for (int i=1;i<=t1;i++) q[ql+i-1]=q1[i];
for (int i=1;i<=t2;i++) q[ql+t1+i-1]=q2[i];
Fourth
solve(ql,ql+t1-1,L,M);
solve(ql+t1,qr,M+1,R);
继续二分,注意区间端点
//这里写代码片
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int INF=1e9+7;
const int mN=100010;
const int mM=10010;
struct node{
int x,y,k,type,id;
};
node q[mN+mM],q1[mN+mM],q2[mN+mM];
int t[mN],ans[mN],n,m,tot=0;
void add(int x,int z) {for (int i=x;i<=n;i+=(i&(-i))) t[i]+=z;}
int ask(int x) {int ans=0;for (int i=x;i>0;i-=(i&(-i))) ans+=t[i];return ans;}
void solve(int ql,int qr,int L,int R)
{
if (ql>qr) return;
if (L==R)
{
for (int i=ql;i<=qr;i++)
if (q[i].type==2) ans[q[i].id]=L;
return;
}
int M=(L+R)>>1; //二分的答案
int t1=0,t2=0;
for (int i=ql;i<=qr;i++)
{
if (q[i].type==1)
{
if (q[i].x<=M) {
add(q[i].id,1);
q1[++t1]=q[i];
}
else q2[++t2]=q[i];
}
else
{
int tt=ask(q[i].y)-ask(q[i].x-1);
if (tt>=q[i].k) q1[++t1]=q[i];
else{
q[i].k-=tt;
q2[++t2]=q[i];
}
}
}
for (int i=1;i<=t1;i++)
if (q1[i].type==1) add(q1[i].id,-1);
for (int i=1;i<=t1;i++) q[ql+i-1]=q1[i];
for (int i=1;i<=t2;i++) q[ql+t1+i-1]=q2[i];
solve(ql,ql+t1-1,L,M);
solve(ql+t1,qr,M+1,R);
}
int main()
{
scanf("%d%d",&n,&m);
int x,y,k;
for (int i=1;i<=n;i++)
{
scanf("%d",&x);
tot++;
q[tot].x=x; q[tot].type=1; q[tot].id=i;
}
for (int i=1;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&k);
tot++;
q[tot].x=x; q[tot].y=y; q[tot].k=k; q[tot].type=2; q[tot].id=i;
}
solve(1,tot,-INF,INF);
for (int i=1;i<=m;i++) printf("%d\n",ans[i]);
return 0;
}