皮卡丘的夢想2(線段樹+二進制狀態壓縮)
Description
一天,一只住在 501 實驗室的皮卡丘決定發奮學習,成為像 LeiQ 一樣的巨巨,於是他向鎮上的賢者金桔請教如何才能進化成一只雷丘。
金桔告訴他需要進化石才能進化,並給了他一個地圖,地圖上有 n 個小鎮,他需要從這些小鎮中收集進化石。
接下來他會進行 q 次操作,可能是打聽進化石的信息,也可能是向你詢問第 l 個小鎮到第 r 個小鎮之間的進化石種類。
如果是打聽信息,則皮卡丘會得到一個小鎮的進化石變化信息,可能是引入了新的進化石,也可能是失去了全部的某種進化石。
如果是向你詢問,你需要回答他第 l 個小鎮到第 r 個小鎮之間的進化石種類。
Input
首先輸入一個整數 T (1 <= T <= 10),代表有 T 組數據。
每組數據的第一行輸入一個整數 n (1 <= n <= 100000) 和一個整數 q (1 <= q <= 100000),分別代表有 n 個小鎮,表皮卡丘有 q 次操作。
接下來輸入 q 行,對於每次操作,先輸入操作類型,然後根據操作類型讀入:
- 1: 緊接著輸入 2 個整數 a (1 <= a <= n), b (1 <= b <= 60),表示第 a 個小鎮引入了第 b 種進化石
- 2: 緊接著輸入 2 個整數 a (1 <= a <= n), b (1 <= b <= 60),表示第 a 個小鎮失去了全部第 b 種進化石
- 3: 緊接著輸入 2 個整數 l, r (1 <= l <= r <= n),表示他想詢問從第 l 個到第 r 個小鎮上可收集的進化石有哪幾種
Output
對於每組輸入,首先輸出一行 "Case T:",表示當前是第幾組數據。
對於每組數據中的每次 3 操作,在一行中按編號升序輸出所有可收集的進化石。如果沒有進化石可收集,則輸出一個 MeiK 的百分號 "%"(不包括引號)。
Sample Input
1 10 10 3 1 10 1 1 50 3 1 5 1 2 20 3 1 1 3 1 2 2 1 50 2 2 20 3 1 2 3 1 10
Sample Output
Case 1: % 50 50 20 50 % %
解題思路:剛好暑假學完了線段樹,我一開始就把這道題當做線段樹的裸題,用線段樹來維護數據,不就是單點更新,區段查詢嘛。但是很不幸時間超限,後來我考慮了原來每次查詢進化石的時候還是要全部遍歷一遍,所以是要超時的。這時我們就需要更高效的方法來存儲和查詢所有進化石的狀態,這種方法就是利用二進制進行狀態壓縮。
我們用二進制數的每一位來表示每種進化石的有無,也即該小鎮進化石的存在狀態。例如,對於有 1、4 號進化石的小鎮,我們可以用二進制數 1001 來表示。它的含義是:從右向左依次表示第 1 個到第 n 個進化石的有無,1 表示有,0 表示無。而且很容易想到,由於每一位只有 0 或 1 兩種可能,且每一位都對應固定的編號,所以對於任意一個二進制數,都能保證唯一對應一種存在狀態。
解決了如何表示存在狀態的問題,下一步就是如何存儲了。例如,當前我們的進化石存在狀態為:1、4,對應二進制 1001,如果我們加入一個 3 號進化石,則應變為 1101,也就是讓倒數第三位變成 1。這裏需要用到位運算:對於 1001,我們讓它與 0100(只含有 3 號石的狀態)進行或運算,即兩數對應的位有一個或兩個為 1 時結果為1,否則為 0,運算結果為 1101。這樣我們使用或運算就可以實現兩個狀態的合並。至於如何表示單個進化石的狀態,很簡單,使用左移運算就可以了,例如:表示 3 號石存在,只需將 1(0001)左移 3-1=2 位即得到 0100。
這樣,我們只需要把二進制和線段樹結合一下就可以愉快地告別 TLE 了。在存儲時,每一個結點都表示它的左右子結點的合並狀態,即對左右子結點進行或運算後的結果,而葉結點直接存儲狀態。在查詢時,只需要遍歷結果對應二進制的每一位來輸出即可。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #define ll long long int 5 const int MAXN=1e5+10; 6 using namespace std; 7 int n,q; 8 ll sum[MAXN<<2]; 9 void push_up(int i)///向上回溯,狀態合並 10 { 11 sum[i]=sum[i<<1]|sum[i<<1|1]; 12 } 13 void build(int l,int r,int rt)///建樹 14 { 15 if(l==r) 16 { 17 sum[rt]=0; 18 return ; 19 } 20 int mid=(r+l)>>1; 21 build(l,mid,rt<<1); 22 build(mid+1,r,rt<<1|1); 23 push_up(rt); 24 } 25 void add(int l,int r,int pos,int v,int rt) 26 { 27 if(l==r) 28 { 29 sum[rt]|=1ll<<(v-1);///1ll為長整型的1 30 return ; 31 } 32 int m=(r+l)>>1; 33 if(m>=pos) 34 { 35 add(l,m,pos,v,rt<<1); 36 } 37 else 38 { 39 add(m+1,r,pos,v,rt<<1|1); 40 } 41 push_up(rt); 42 } 43 void del(int l,int r,int pos,int val,int rt) 44 { 45 if(l==r) 46 { 47 sum[rt]&=~(1ll<<(val-1)); 48 return ; 49 } 50 int mid=(r+l)>>1; 51 if(pos<=mid) 52 { 53 del(l,mid,pos,val,rt<<1); 54 } 55 else 56 { 57 del(mid+1,r,pos,val,rt<<1|1); 58 } 59 push_up(rt); 60 } 61 ll query(int l,int r,int L,int R,int rt)///區間查詢 62 { 63 if(L<=l&&R>=r) 64 { 65 return sum[rt]; 66 } 67 ll ans=0; 68 int mid=(r+l)>>1; 69 if(L<=mid) 70 { 71 ans|=query(l,mid,L,R,rt<<1); 72 } 73 if(R>mid) 74 { 75 ans|=query(mid+1,r,L,R,rt<<1|1); 76 } 77 return ans; 78 } 79 80 int main() 81 { 82 int i,t; 83 int op,x,y; 84 scanf("%d",&t); 85 for(i=1; i<=t; i++) 86 { 87 printf("Case %d:\n",i); 88 scanf("%d%d",&n,&q); 89 build(1,n,1); 90 while(q--) 91 { 92 scanf("%d",&op); 93 scanf("%d%d",&x,&y); 94 if(op==1) 95 { 96 add(1,n,x,y,1); 97 } 98 if(op==2) 99 { 100 del(1,n,x,y,1); 101 } 102 if(op==3) 103 { 104 ll ans=query(1,n,x,y,1); 105 ll cnt=0; 106 int flag=1; 107 int ot=1; 108 while(ans) 109 { 110 if(ans&1) 111 { 112 if(flag) 113 { 114 flag=0; 115 } 116 else 117 { 118 printf(" "); 119 } 120 printf("%d",ot); 121 } 122 ot++; 123 ans>>=1; 124 } 125 if(flag) 126 { 127 printf("%%\n"); 128 } 129 else 130 { 131 printf("\n"); 132 } 133 } 134 } 135 } 136 return 0; 137 }
皮卡丘的夢想2(線段樹+二進制狀態壓縮)