1. 程式人生 > >POJ 2104 K-th Number (劃分樹,主席樹寫過了,這次是整體二分解法 )

POJ 2104 K-th Number (劃分樹,主席樹寫過了,這次是整體二分解法 )

還是先描述一下題意:

給出一個長度為n的數列,m次詢問區間內的第k大數

對劃分樹,主席樹和整體二分通過這題做了一下比較

劃分樹  1000ms+

主席樹 2000ms+

整體二分 1500ms+

整體二分介於兩者之前,對於這題複雜度約莫是O( (n+m)log(n+m)log( Range( ans )  ) )

整體二分這個東西比較奇妙,運用的是離線演算法,而主席樹和劃分樹都是線上的

先引用一下2013年許昊然論文-《淺談資料結構題的幾個非經典解法》解釋一下整體二分

這裡寫圖片描述

此題整體二分思路:

1.確定答案在l~r這個區間內

2.取二分中值mid,詢問所有查詢操作在陣列中小於等於mid的情況下,有多少個數在查詢區間內

3.由此將查詢分為兩類

   q1: 區間內個數大於等於k

   q2:區間內個數小於k

可以看出q1情況下的查詢應該縮小答案,q2情況下的查詢應該放大答案,

同時q2情況下記錄mid對對答案的影響值cur(有點類似於cdq分治思想)

由此為依據對陣列值和查詢操作一起進行二分,回到步驟1一直到得到所有答案

此處統計個數用樹狀陣列簡潔方便

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<string>
#include<iostream>

using namespace std;

#define INF 0x3f3f3f3f

struct node
{
    int l,r,k,val;
    int cur,index;
    int kind;
} q[200005],q1[100005],q2[100005];

int n,m;
int ans[100006];
int c[100006];
int tmp[200006];

void init()
{
    memset(c,0,sizeof c);
    memset(tmp,0,sizeof tmp);
    for(int i=1; i<=m+n; i++)
    {
        q[i].cur=q1[i].cur=q2[i].cur=0;
    }
}

inline int lowbit(int x)
{
    return x&-x;
}

inline void update(int x,int val)
{
    for(; x<=n; x+=lowbit(x)) c[x]+=val;
}

inline int query(int x)
{
    int sum=0;
    for(; x>0; x-=lowbit(x)) sum+=c[x];
    return sum;
}

void divide(int s,int t,int l,int r)
{
    if(s>t) return ;
    if(l==r)
    {
        for(int i=s; i<=t; i++)
            if(q[i].kind==2) ans[q[i].index]=l;
        return ;
    }
    int mid=(l+r)>>1;
    int num1=0,num2=0,flag1=0,flag2=0;
    for(int i=s; i<=t; i++)
    {
        if(q[i].kind==1)
        {
            if(q[i].val<=mid) update(q[i].index,1),q1[num1++]=q[i];
            else q2[num2++]=q[i];
        }
        else if(q[i].kind==2)
        {
            tmp[i]=query(q[i].r)-query(q[i].l-1);
            if(q[i].cur+tmp[i]>=q[i].k) q1[num1++]=q[i],flag1=1;
            else q[i].cur+=tmp[i],q2[num2++]=q[i],flag2=1;
        }
    }
    for(int i=s; i<=t; i++)
    {
        if(q[i].kind==1&&q[i].val<=mid) update(q[i].index,-1);
    }
    for(int i=0; i<num1; i++) q[s+i]=q1[i];
    for(int i=0; i<num2; i++) q[s+num1+i]=q2[i];
    if(flag1)  divide(s,s+num1-1,l,mid);
    if(flag2)  divide(s+num1,t,mid+1,r);
}

int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        init();
        int cnt=1;
        for(int i=1; i<=n; i++)
        {
            scanf("%d",&q[cnt].val);
            q[cnt].kind=1;
            q[cnt].index=i;
            cnt++;
        }
        for(int i=1; i<=m; i++)
        {
            q[cnt].index=i;
            scanf("%d%d%d",&q[cnt].l,&q[cnt].r,&q[cnt].k);
            q[cnt].kind=2;
            cnt++;
        }
        divide(1,cnt-1,-INF,INF);
        for(int i=1; i<=m; i++) printf("%d\n",ans[i]);
    }
    return 0;
}