poj2104 K-th Number(整体二分+树状数组)

Description

You are working for Macrohard company in data structures department. After failing your previous task about key insertion you were asked to write a new data structure that would be able to return quickly k-th order statistics in the array segment.

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 first line of the input file contains n — the size of the array, and m — the number of questions to answer (1 <= n <= 100 000, 1 <= m <= 5 000).

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

For each question output the answer to it — the k-th number in sorted a[i…j] segment.

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;
} 
  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值