1. 程式人生 > >poj2104 K-th Number(整體二分+樹狀陣列)

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 109 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


分析:
整體二分的例題
在這裡再說一下整體二分的實現:

  • 確定一個區間以及這個區間對應的答案範圍
  • 確定當前的判斷標準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;
}