noi2017 T1 整數 ——線段樹
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 整數 ——線段樹