1. 程式人生 > >BZOJ3110[Zjoi2013]K大數查詢(樹狀陣列+整體二分)

BZOJ3110[Zjoi2013]K大數查詢(樹狀陣列+整體二分)

3110 [Zjoi2013]K大數查詢

有N個位置,M個操作。操作有兩種,每次操作如果是1 a b c的形式表示在第a個位置到第b個位置,每個位置加入一個數c
如果是2 a b c形式,表示詢問從第a個位置到第b個位置,第C大的數是多少。

Input

第一行N,M
接下來M行,每行形如1 a b c或2 a b c

Output

輸出每個詢問的結果

Sample Input

2 5
1 1 2 1
1 1 2 2
2 1 1 2
2 1 1 1
2 1 2 3

Sample Output

1
2
1

HINT

 



【樣例說明】

第一個操作 後位置 1 的數只有 1 , 位置 2 的數也只有 1 。 第二個操作 後位置 1

的數有 1 、 2 ,位置 2 的數也有 1 、 2 。 第三次詢問 位置 1 到位置 1 第 2 大的數 是

1 。 第四次詢問 位置 1 到位置 1 第 1 大的數是 2 。 第五次詢問 位置 1 到位置 2 第 3

大的數是 1 。‍


N,M<=50000,N,M<=50000

a<=b<=N

1操作中abs(c)<=N

2操作中c<=Maxlongint

 題解:


題意概括

  有N個位置,M個操作。操作有兩種,每次操作如果是1 a b c的形式表示在第a個位置到第b個位置,每個位置加入一個數c。如果是2 a b c形式,表示詢問從第a個位置到第b個位置,第C大的數是多少。

N,M<=50000
a<=b<=N
1操作中abs(c)<=N
2操作中c<=Maxlongint


題解

  讓我們來考慮神奇的分治演算法。

  整體二分!!(當你會了)

  首先當你已經掌握了樹狀陣列的區間加和區間詢問(如果不會->點這裡

  我們考慮二分答案。

  注意進行以下操作要嚴格按照輸入時間先後順序來。

  首先對於加進去的數字c,我們把他變成n-c+1,這樣就把詢問前k大變成了前k小。

  如果是修改操作,如果修改的值比當前的mid值小,就修改,並扔到左區間裡面。否則扔到右邊。

  如果是詢問操作,如果在當前的狀態下,該詢問的區間內查詢到的數的個數res比當前詢問的c要大(或者相等),那麼顯然答案在左區間,把他扔到左邊,否則把他的c減掉res再扔到右邊去。

  然後遞迴分治兩個區間就可以了。

  (本質是個二分答案的升級版)

  

參考程式碼:

/**************************************************************
    Problem: 3110
    User: SongHL
    Language: C++
    Result: Accepted
    Time:1880 ms
    Memory:3640 kb
****************************************************************/
 
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&-x
#define clr(a,b) memset(a,b,sizeof a) 
typedef long long ll;
const int maxn=50005;
int n,m,id[maxn],templ[maxn],tempr[maxn];
ll tree[2][maxn];
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') {x=x*10+ch-'0'; ch=getchar();}
    return x*f;
}
inline void add(int t,int x,int y)//單點加 
{
    while(x<=n+1)
    {
        tree[t][x]+=y;
        x+=lowbit(x);
    }
}
inline void update(int l,int r,int val)//區間加 
{
    add(0,l,val);add(1,l,l*val);
    add(0,r+1,-val);add(1,r+1,-val*(r+1));
}
inline ll Sum(int t,int x)//字首和 
{
    ll ans=0;
    while(x>0)
    {
        ans+=tree[t][x];
        x-=lowbit(x);
    }
    return ans;
}
inline ll query(int l,int r)//區間求和 
{
    return Sum(0,r)*(r+1)-Sum(0,l)*l-Sum(1,r)+Sum(1,l);
}
struct Node{
    int type,l,r,x,ans;
    void Get()
    {
        type=read();l=read();r=read();x=read();
        if(type==1) x=n-x+1;
    }
} a[maxn];
 
inline void Solve(int lx,int rx,int l,int r)
{
    if(l>r) return ;
    if(lx==rx)
    {
        for(int i=l;i<=r;++i) a[id[i]].ans=lx;
        return ;
    }
    int midx=lx+rx>>1;
    int L=0,R=0;
    for(int i=l;i<=r;++i)
    {
        if(a[id[i]].type==1)
        {
            if(a[id[i]].x<=midx) templ[++L]=id[i],update(a[id[i]].l,a[id[i]].r,1);
            else tempr[++R]=id[i];
        }
        else
        {
            ll res=query(a[id[i]].l,a[id[i]].r);
            if(res>=a[id[i]].x) templ[++L]=id[i];
            else tempr[++R]=id[i],a[id[i]].x-=res;
        }
    }
    for(int i=1;i<=L;++i)
    {
        if(a[templ[i]].type==1)
            update(a[templ[i]].l,a[templ[i]].r,-1); 
    }
     
    for(int i=l;i<=l+L-1;++i) id[i]=templ[i-(l-1)];
    for(int i=r-R+1;i<=r;++i) id[i]=tempr[i-(r-R)];
     
    Solve(lx,midx,l,l+L-1);
    Solve(midx+1,rx,r-R+1,r);   
}
 
int main()
{
    n=read();m=read();
    for(int i=1;i<=m;++i) a[i].Get(),id[i]=i;
    clr(tree,0);
    Solve(1,2*n+1,1,m);
    for(int i=1;i<=m;++i)
        if(a[i].type==2) printf("%d\n",n-a[i].ans+1);
    return 0;   
}