1. 程式人生 > >noi2017 T1 整數 ——線段樹

noi2017 T1 整數 ——線段樹

mod 大於 答案 超級計算機 標準 nbsp i++ 研究 兩個

loj.ac上有 題目傳送門

不過我還是把題目搬過來吧

整數(integer)
【題目背景】
在人類智慧的山巔,有著一臺字長為 1048576 位的超級計算機,著名理論計算機科 學家 P 博士正用它進行各種研究。不幸的是,這天臺風切斷了電力系統,超級計算機 無法工作,而 P 博士明天就要交實驗結果了,只好求助於學過 OI 的你......
【題目描述】 P 博士將他的計算任務抽象為對一個整數的操作。

具體來說,有一個整數 x ,一開始為 0。 接下來有 n 個操作,每個操作都是以下兩種類型中的一種:

? 1 a b :將 x 加上整數 a·2b,其中 a 為一個整數,b 為一個非負整數

? 2 k :詢問 x 在用二進制表示時,位權為 2k 的位的值(即這一位上的 1 代表 2k )

保證在任何時候,x≥0。
【輸入格式】 從標準輸入讀入數據。

輸入的第一行包含四個正整數 n,t1,t2,t3,n 的含義見題目描述,t1,t2,t3 的具體含義 見子任務。

接下來 n 行,每行給出一個操作,具體格式和含義見題目描述。

同一行輸入的相鄰兩個元素之間,用恰好一個空格隔開。
【輸出格式】

輸出到標準輸出。

對於每個詢問操作,輸出一行,表示該詢問的答案(000 或 111)。 對於加法操作,沒有任何輸出

樣例輸入 1

10 3 1 2

1 100 0

1 2333 0

1 -233 0

2 5

2 7

2 15

1 5 15

2 15

1 -1 12

2 15

【樣例 1 輸出】

0

1

0

1

0

技術分享

【子任務】 在所有測試點中,

1 ≤ t1 ≤ 3,1 ≤ t2 ≤ 4,1 ≤ t3 ≤ 2。

不同的 t1,t2,t3 對應的特殊限制 如下:

? 對於 t1 = 1 的測試點,滿足 a = 1

?對於 t1 = 2 的測試點,滿足 |a|= 1

? 對於 t1 = 3 的測試點,滿足 |a|≤109 ? 對於 t2 = 1 的測試點,滿足 0≤b,k≤30

? 對於 t2 = 2 的測試點,滿足 0≤b,k≤100

? 對於 t2 = 3 的測試點,滿足 0≤b,k≤n

? 對於 t2 = 4 的測試點,滿足 0≤b,k≤30n

? 對於 t3 = 1 的測試點,保證所有詢問操作都在所有修改操作之後

? 對於 t3 = 2 的測試點,不保證詢問操作和修改操作的先後順序

技術分享

這道題首先 a*2^b 這個東西呢按我的想法是把 a 拆成2的x次冪的和 然後+b

如果只有加法當然很好寫 但是這道題 a 可能是負的

所以我開了兩個數組 s1 s2 來存 s1 存的是正的 s2 存的是負的

註意存的時候進位 均攤復雜度只有o(1) 所以暴力進位就好了

然後問題就轉換成了已知兩個高精度數,問他們的差的某一位是什麽

然後分類可得

我們記一個數為 1(0)x

1 (0) 表示詢問的這一位的值(因為是二進制,所以不是1就是0)

x就是詢問位後面那一串東西(一坨1 0 混合體)

考慮減法是否退位的時候呢

1. x>=y:
1x-1y=0(x-y)
1x-0y=1(x-y)
0x-1y=1(x-y)
0x-0y=0(x-y)
2. x<y:
1x-1y=1(x-y+2^k)
1x-0y=0(x-y+2^k)
0x-1y=0(x-y+2^k)
0x-0y=1(x-y+2^k)

舉個例子吧

10-10=00
10-00=10
01-10=11
01-01=00

10-11=11
10-01=01
00-11=01
00-01=11

註 題目保證x始終大於等於0 所以s1總和也一定大於s2

這樣分析之後呢 問題就轉換成了從1-x(即詢問位)-1這個區間裏s1 s2 的大小關系

如果 s1>=s2

那麽答案就是 s1^s2(^念做異或)

如果 s2<=s2

那麽答案就是 s1==s2 (==念做同或)

那麽 x 和 y 的比較 其實就是比較字典序 找到第一個不一樣的地方 哪個是1 哪個就大 (這麽很好證明我就不詳細講了) 註意這裏講的都是二進制

算了舉個例子吧 如果x是1000 y是0111 那麽x是不是一定大於y

所以我們可以采用線段樹維護區間內s1 s2 的大小

當然我這裏采用的是zkw線段樹 (自帶底層優化自然會比較快 而且修改也很好寫)

畢竟三千萬(3e7)個葉子結點 同時線段樹中下標也就是二進制中 2的次數

操作自然就是 區間內單點修改,前綴查詢

那麽我們每次操作1拆完a加上b以及進位之後

我們要記錄一波 修改到的最左位置到最右位置 進位自然也算修改

然後就在線段樹上暴力修改順便上傳信息 復雜度是 o( r-l + log(n))

操作2 詢問的時候我們就找到區間中靠右(也就是比較大的位置)的第一個不一樣的地方然後判斷一波就好了

到這裏題目就完美解決了 修改(modify)以及 查找(find) 就看代碼吧

註: find我的寫法呢 是向上走時,如果x是右孩子且x的兄弟為1才向下走

實測跑得也蠻快的 23333

技術分享

技術分享
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define LL long long 
using namespace std;
const int M=3e7+31,N=1<<25;
int read(){
    int ans=0,f=1,c=getchar();
    while(c<0||c>9){if(c==-) f=-1; c=getchar();}
    while(c>=0&&c<=9){ans=ans*10+(c-0); c=getchar();}
    return ans*f;
}
int s1[M],s2[M],cnt;
int l,r,a,b,n,k;
void prepare(int x){
    if(!x) return ;
    int v=x>0?x:-x,w[31]; 
    cnt=0;
    for(int i=30;i>=0;i--){
        int now=1<<i;
        if(now&v) v-=now,w[++cnt]=i;
        if(!v) break;
    }
    l=w[cnt]+b; r=w[1]+b;
    if(x>0)
    for(int i=1;i<=cnt;i++){ 
        int now=w[i]+b; 
        while(s1[now]) s1[now++]=0,r=max(r,now);
        s1[now]=1;
    }
    else
    for(int i=1;i<=cnt;i++){
        int now=w[i]+b; 
        while(s2[now]) s2[now++]=0,r=max(r,now);
        s2[now]=1;
    }
}
int tr[2*N],T,now;
void modify(){
    for(int i=l;i<=r;i++) tr[i+N]=s1[i]^s2[i];
    for(l=(l+N)>>1,r=(r+N)>>1;l;l>>=1,r>>=1)
    for(int i=l;i<=r;i++) tr[i]=tr[i<<1]|tr[i<<1^1];
}
int find(int k){
    for(k+=N;k;k>>=1) if(k&1&tr[k^1]){
        for(k^=1;k<N;k=k<<1^tr[k<<1^1]);
        return k-N;
    }
    return -1;
}
int main()
{
    n=read(); T=read(); T=read(); T=read();
    for(int i=1;i<=n;i++){
        k=read();
        if(k==1){
            a=read(); b=read(); 
            prepare(a);
            modify();
        }
        else{
            a=read(); 
            now=find(a);
            if(s1[now]>s2[now]||now==-1) printf("%d\n",s1[a]^s2[a]);
            else printf("%d\n",s1[a]==s2[a]);
        }
    }
    return 0;
}
View Code

noi2017 T1 整數 ——線段樹