1. 程式人生 > >bzoj 3065 帶插入區間K小值

bzoj 3065 帶插入區間K小值

3065: 帶插入區間K小值
Time Limit: 60 Sec Memory Limit: 512 MB
Submit: 2627 Solved: 784
[Submit][Status][Discuss]
Description

從前有n只跳蚤排成一行做早操,每隻跳蚤都有自己的一個彈跳力a[i]。跳蚤國王看著這些跳蚤國欣欣向榮的情景,感到非常高興。這時跳蚤國王決定理性愉悅一下,查詢區間k小值。他每次向它的隨從伏特提出這樣的問題: 從左往右第x個到第y個跳蚤中,a[i]第k小的值是多少。
這可難不倒伏特,他在腦袋裡使用函式式線段樹字首和的方法水掉了跳蚤國王的詢問。
這時伏特發現有些跳蚤跳久了彈跳力會有變化,有的會增大,有的會減少。
這可難不倒伏特,他在腦袋裡使用樹狀陣列套線段樹的方法水掉了跳蚤國王的詢問。(orz 主席樹)
這時伏特發現有些遲到的跳蚤會插入到這一行的某個位置上,他感到非常生氣,因為……他不會做了。
請你幫一幫伏特吧。
快捷版題意:帶插入、修改的區間k小值線上查詢。

Input

第一行一個正整數n,表示原來有n只跳蚤排成一行做早操。
第二行有n個用空格隔開的非負整數,從左至右代表每隻跳蚤的彈跳力。
第三行一個正整數q,表示下面有多少個操作。
下面一共q行,一共三種操作對原序列的操作:(假設此時一共m只跳蚤)
1. Q x y k: 詢問從左至右第x只跳蚤到從左至右第y只跳蚤中,彈跳力第k小的跳蚤的彈跳力是多少。
(1 <= x <= y <= m, 1 <= k <= y - x + 1)
2. M x val: 將從左至右第x只跳蚤的彈跳力改為val。
(1 <= x <= m)
3. I x val: 在從左至右第x只跳蚤的前面插入一隻彈跳力為val的跳蚤。即插入後從左至右第x只跳蚤是我剛插入的跳蚤。
(1 <= x <= m + 1)

為了體現線上操作,設lastAns為上一次查詢的時候程式輸出的結果,如果之前沒有查詢過,則lastAns = 0。
則輸入的時候實際是:
Q _x _y _k ——> 表示 Q _x^lastAns _y^lastAns _k^lastAns
M _x _val ——> 表示 M _x^lastAns _val^lastAns
I _x _val ——> 表示 I _x^lastAns _val^lastAns
簡單來說就是操作中輸入的整數都要異或上一次詢問的結果進行解碼。

(祝Pascal的同學早日轉C++,就不提供pascal版的描述了。)

Output

對於每個詢問輸出回答,每行一個回答。

Sample Input

10

10 5 8 28 0 19 2 31 1 22

30

I 6 9

M 1 11

I 8 17

M 1 31

M 6 26

Q 2 7 6

I 23 30

M 31 7

I 22 27

M 26 18

Q 26 17 31

I 5 2

I 18 13

Q 3 3 3

I 27 19

Q 23 23 30

Q 5 13 5

I 3 0

M 15 27

Q 0 28 13

Q 3 29 11

M 2 8

Q 12 5 7

I 30 19

M 11 19

Q 17 8 29

M 29 4

Q 3 0 12

I 7 18

M 29 27

Sample Output

28

2

31

0

14

15

14

27

15

14

HINT

此題作為一個小小的研究來搞吧~做法有很多~不知道這題究竟有多少種做法。

請自覺O(log^2n),我故意卡塊狀連結串列,塊鏈A了的請受我深情一拜……

A掉的同學請在Discuss裡面簡要說下自己的做法吧~

原序列長度 <= 35000

插入個數 <= 35000,修改個數 <= 70000,查詢個數 <= 70000 ,0 <= 每時每刻的權值 <= 70000

由於是OJ上的題,所以資料無梯度。為了防止卡OJ,本題只有4組資料。

【分析】
本蒟蒻做分塊演算法的第一題。由於實在不會花式線段樹,只能通過這種半暴力的方式來做。程式碼借鑑swm_sxt,在此表示感謝。
A題無力啊啊啊。
對於本題,將所有數分成num個連續區間,除最後一個區間外每個區間中存放600個元素。對每個區間進行排序處理,當然也要保留原來未排序的區間,當查詢l~r之間的第k小值時,我們可以二分答案(權值<=70000)。對於被完全覆蓋的塊,可以進行二分查詢,未完全覆蓋的塊,進行暴力查詢。
當改變元素時,暴力找出元素位置,然後對於元素所在區間插入排序即可(當然也可以用二分)。
當新增元素時,如果新增的元素所在區間元素個數≥2b,即1200時,要對區間進行“分裂”,將區間個數num++,然後把後600個元素新增進num區間,前600個新增進原區間。然後用後繼表示法:假設所在區間為i,那麼 next[num]=next[i],next[i]=num 即可。

總複雜度O(m*log²n)

【程式碼】

//bzoj 3065 帶插入區間K小值
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,m,b,x,l,r,num,ans,lc,rc,li,ri,v;
char ch[3];
int ne[201],s[201],k[201][1205],a[201][1205];
inline int que(int i,int e)
{
    int ll=0,rr=s[i]-1,mid;
    while(ll<rr)
    {
        mid=(ll+rr+1)/2;
        if(k[i][mid]>=e) rr=mid-1;
        else ll=mid;
    }
    if(k[i][ll]<e) ll++;
    return ll;
} 
inline int ask()   
{
    scanf("%d%d%d",&l,&r,&x);
    l^=ans,r^=ans,x^=ans;
    lc=rc=0;
    //找出l,r所在區間及所在的位置 
    for(li=0;li!=-1;li=ne[li])
      if((lc=lc+s[li])>=l) break;
    lc=l-lc+s[li]-1;
    for(ri=0;ri!=-1;ri=ne[ri])
      if((rc=rc+s[ri])>=r) break;
    rc=r-rc+s[ri]-1;
    //二分答案 
    int ll=0,rr=70000,mid,t;
    while(ll<rr)
    {
        mid=(ll+rr+1)/2,t=0;
        if(li!=ri)  //不在同一個塊
        {
            for(int i=ne[li];i!=ri;i=ne[i])
              t+=que(i,mid); //找出被區間完全覆蓋的塊中的答案 
            for(int i=lc;i<s[li];i++)  //暴力查詢部分覆蓋區間 
              if(a[li][i]<mid) t++; 
            for(int i=0;i<=rc;i++)
              if(a[ri][i]<mid) t++;
        }
        else
          for(int i=lc;i<=rc;i++)
            if(a[li][i]<mid) t++;
        if(t>=x) rr=mid-1;
        else ll=mid;
    }
    return ll;
}
inline void change()  //暴力修改 
{
    scanf("%d%d",&x,&v);
    x^=ans,v^=ans;
    lc=0;int i,j;
    for(i=0;i!=-1;i=ne[i])
      if((lc=lc+s[i])>=x) break;
    lc=x-lc+s[i]-1;
    for(j=0;j<s[i];j++)
      if(k[i][j]==a[i][lc]) break;
    k[i][j]=a[i][lc]=v;
    while(j && k[i][j]<k[i][j-1])
      swap(k[i][j],k[i][j-1]),j--;
    while(j<s[i]-1 && k[i][j]>k[i][j+1])
      swap(k[i][j],k[i][j+1]),j++;
}
inline void in()
{
    scanf("%d%d",&x,&v);
    x^=ans,v^=ans;
    lc=0;
    int j;
    for(li=0;li!=-1;li=ne[li])
    {
        lc+=s[li];
        if(lc>=x || ne[li]==-1) break;
    }
    x=x-lc+s[li]-1;
    for(j=s[li];j>x;j--) a[li][j]=a[li][j-1];
    s[li]++;a[li][x]=v;
    if(s[li]==2*b)
    {
        num++;
        s[num]=b;
        s[li]=b;
        ne[num]=ne[li];
        ne[li]=num;
        for(j=0;j<b;j++)
          k[num][j]=a[num][j]=a[li][b+j],
          k[li][j]=a[li][j];
        sort(k[li],k[li]+b);
        sort(k[num],k[num]+b);
    } 
    else
    {
        for(j=0;j<s[li]-1;j++)
          if(v<k[li][j]) break;
        int tmp=j;
        for(j=s[li]-1;j>tmp;j--)
          k[li][j]=k[li][j-1];
        k[li][tmp]=v;
    }
}
int main()
{
    scanf("%d",&n);
    b=600;
    for(int i=0;i<n;i++)
    {
        scanf("%d",&x);
        a[i/b][i%b]=k[i/b][i%b]=x;
    }
    num=(n+b-1)/b-1;
    for(int i=0;i<num;i++)
      ne[i]=i+1,s[i]=b;
    ne[num]=-1;
    s[num]=n-(b*num);
    for(int i=0;i<=num;i++)
      sort(k[i],k[i]+s[i]);
    scanf("%d",&m);
    while(m--)
    {
        scanf("%s",ch);
        if(ch[0]=='Q')
          printf("%d\n",ans=ask());
        else if(ch[0]=='M') change();
        else in();
    }
    return 0;
}