poj2104 K-th Number(整體二分+樹狀陣列)
阿新 • • 發佈:2019-02-09
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 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
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;
}