分塊,是一種可以說是,相當,暴力的數據結構。
分塊算法的思想是通過適當的划分,預處理一部分信息保存下來,用空間換取時間,達到時空平衡。
基本操作是,將一段序列,分成一定數量的塊,每一塊有一個長度,表示一段區間。對於區間操作,通過對完整塊的整體操作和對不完整塊的暴力操作而使復雜度盡可能的低
一般來講,塊的大小常設為sqrt(n),但實際上塊的大小可以任意自定,不過肯定是要讓復雜度盡可能的優秀
分塊的效率要低於樹狀數組和線段樹,且代碼實現比較隨意,相對來說更好理解。
但是俗話說的好:越簡單的東西,就意味着它越牢固,可拓展性越強。
分塊與其說是數據結構,它更像是一種思想,就像分治這樣,雖然分塊的整體框架也基本固定。
分塊與樹狀數組,線段樹相比,我個人認為,它最大的優點在於,它本身極為強大的可拓展性。
也就是說分塊可以容納相當多的區間操作,這也是由分塊本身的性質決定的。
分塊更多的像是一個思想。
分塊實現的基本框架:
划分塊,預處理,操作或查詢。
操作或查詢通常為4步:
1.判斷要操作或是查詢的區間是否在一個塊內
2.若在一個塊內,暴力操作或查詢
3.若不在一個塊內,將除了最左邊和最右邊這兩個塊外其余的塊進行整體的操作,即直接對塊打上修改標記之類的
4.單獨暴力處理最左邊的塊和最右邊的塊
同時分塊的塊內還可以使用別的數據結構或是操作以實現要求或進一步優化復雜度
分塊例題1—9(loj)當然更建議直接看黃學長的博客
分塊入門1:
給出一個長為 n 的數列,以及 n 個操作,操作涉及區間加法,單點查值。
1 #include<bits/stdc++.h>
2 using namespace std; 3 const int maxn = 50086; 4 int a[maxn], n; 5 int add[maxn]; 6 int pos[maxn];//, sum[maxn];
7 int L[maxn], R[maxn]; 8
9 inline int read() { 10 int x = 0, y = 1; 11 char ch = getchar(); 12 while(!isdigit(ch)) { 13 if(ch == '-') y = -1; 14 ch = getchar(); 15 } 16 while(isdigit(ch)) { 17 x = (x << 1) + (x << 3) + ch - '0'; 18 ch = getchar(); 19 } 20 return x * y; 21 } 22
23 inline void change(int l, int r, int d) { 24 int p = pos[l], q = pos[r]; 25 if(p == q) 26 for(int i = l; i <= r; ++i) a[i] += d; 27 else { 28 for(int i = p + 1; i <= q - 1; ++i) add[i] += d; 29 for(int i = l; i <= R[p]; ++i) a[i] += d; 30 for(int i = L[q]; i <= r; ++i) a[i] += d; 31 } 32 } 33
34 int main() { 35 n = read(); 36 for(int i = 1; i <= n; ++i) 37 a[i] = read(); 38 int t = sqrt(n); 39 for(int i = 1; i <= t; ++i) { 40 L[i] = (i - 1) * t + 1; 41 R[i] = i * t; 42 } 43 if(R[t] < n) t++, L[t] = R[t - 1] + 1, R[t] = n; 44 for(int i = 1; i <= t; ++i) 45 for(int j = L[i]; j <= R[i]; ++j) 46 pos[j] = i; 47 for(int i = 1; i <= n; ++i) { 48 int opt, l, r, c; 49 opt = read(), l = read(), r = read(), c = read(); 50 if(!opt) change(l, r, c); 51 else { 52 int q = pos[r]; 53 cout << a[r] + add[q] << '\n'; 54 } 55 } 56 return 0; 57 }
分塊入門2:
給出一個長為 n 的數列,以及 n 個操作,操作涉及區間加法,詢問區間內小於某個值 x 的元素個數。
1 #include<bits/stdc++.h>
2 #define ll long long
3 using namespace std; 4 const int maxn = 50005; 5 int n,blo; 6 int v[maxn], bl[maxn], atag[maxn]; 7 vector<int>ve[505]; 8
9 inline ll read() { 10 ll x = 0, y = 1; 11 char ch = getchar(); 12 while(!isdigit(ch)) { 13 if(ch == '-') y = -1; 14 ch = getchar(); 15 } 16 while(isdigit(ch)) { 17 x = (x << 1) + (x << 3) + ch - '0'; 18 ch = getchar(); 19 } 20 return x * y; 21 } 22
23 inline void reset(int x) { 24 ve[x].clear(); 25 for(int i=(x-1)*blo+1;i<=min(x*blo,n);i++) 26 ve[x].push_back(v[i]); 27 sort(ve[x].begin(),ve[x].end()); 28 } 29
30 inline void add(int a, int b, int c) { 31 for(int i = a; i <= min(bl[a] * blo, b); i++) 32 v[i] += c; 33 reset(bl[a]); 34 if(bl[a] != bl[b]) { 35 for(int i = (bl[b] - 1) * blo + 1; i <= b; i++) 36 v[i] += c; 37 reset(bl[b]); 38 } 39 for(int i =bl[a] + 1; i <= bl[b] - 1; i++) 40 atag[i] +=c; 41 } 42
43 inline int query(int a, int b, int c) { 44 int ans = 0; 45 for(int i = a; i <= min(bl[a] * blo, b); i++) 46 if(v[i] + atag[bl[a]] < c)ans++; 47 if(bl[a] != bl[b]) 48 for(int i = (bl[b] - 1) * blo + 1; i <= b; i++) 49 if(v[i] + atag[bl[b]] < c)ans++; 50 for(int i=bl[a]+1;i<=bl[b]-1;i++) { 51 int x = c - atag[i]; 52 ans += lower_bound(ve[i].begin(), ve[i].end(), x) - ve[i].begin(); 53 } 54 return ans; 55 } 56
57 int main() { 58 n = read(); blo = sqrt(n);//t = sqrt(n);
59 for(int i = 1; i <= n; ++i) v[i] = read();//a[i] = read()
60 for(int i = 1; i <= n; i++) { 61 bl[i] = (i - 1) / blo + 1;//pos[i]
62 ve[bl[i]].push_back(v[i]); 63 } 64 for(int i = 1; i <= bl[n]; i++) 65 sort(ve[i].begin(), ve[i].end()); 66 for(int i = 1; i <= n; i++) { 67 int opt = read(), l = read(), r = read(), d = read(); 68 if(opt == 0) add(l, r, d); 69 if(opt == 1) printf("%d\n", query(l, r, d * d)); 70 } 71 return 0; 72 }
分塊入門3:
給出一個長為 n 的數列,以及 n 個操作,操作涉及區間加法,詢問區間內小於某個值 x 的前驅(比其小的最大元素)。
因為要使用set(聽說二分有點難卡過去),所以沒寫
分塊入門4:
給出一個長為 n 的數列,以及 n 個操作,操作涉及區間加法,區間求和
傳說中的經典操作
1 #include<bits/stdc++.h>
2 #define ll long long
3 using namespace std; 4 const int maxn = 50086; 5 int n, t; 6 ll add[maxn], sum[maxn], a[maxn]; 7 int L[maxn], R[maxn]; 8 int pos[maxn]; 9 int opt, l, r, d; 10
11 inline int read() { 12 int x = 0, y = 1; 13 char ch = getchar(); 14 while(!isdigit(ch)) { 15 if(ch == '-') y = -1; 16 ch = getchar(); 17 } 18 while(isdigit(ch)) { 19 x = (x << 1) + (x << 3) + ch - '0'; 20 ch = getchar(); 21 } 22 return x * y; 23 } 24
25 inline void change(int l, int r, int d) { 26 int p = pos[l], q = pos[r]; 27 if(p == q) { 28 for(int i = l; i <= r; ++i) a[i] += d; 29 sum[p] += (r - l + 1) * d; 30 } 31 else { 32 for(int i = p + 1; i <= q - 1; ++i) add[i] += d; 33 for(int i = l; i <= R[p]; ++i) a[i] += d; 34 sum[p] += (R[p] - l + 1) * d; 35 for(int i = L[q]; i <= r; ++i) a[i] += d; 36 sum[q] += (r - L[q] + 1) * d; 37 } 38 } 39
40 inline ll ask(int l, int r, int mod) { 41 int p = pos[l], q = pos[r]; 42 ll ans = 0; 43 if(p == q) { 44 for(int i = l; i <= r; ++i) ans = (ans + a[i]) % mod; 45 ans = (ans + add[p] * (r - l + 1)) % mod; 46 } 47 else { 48 for(int i = p + 1; i <= q - 1; ++i) 49 ans = (ans + sum[i] + add[i] * (R[i] - L[i] + 1)) % mod; 50 for(int i = l; i <= R[p]; ++i) ans = (ans + a[i]) % mod; 51 ans = (ans + add[p] * (R[p] - l + 1)) % mod; 52 for(int i = L[q]; i <= r; ++i) ans = (ans + a[i]) % mod; 53 ans = (ans + add[q] * (r - L[q] + 1)) % mod; 54 } 55 return ans % mod; 56 } 57
58 int main() { 59 // freopen("asd.in", "r", stdin); 60 // freopen("a.out", "w", stdout);
61 n = read(); t = sqrt(n); 62 for(int i = 1; i <= n; ++i) a[i] = read(); 63 for(int i = 1; i <= t; ++i) { 64 L[i] = (i - 1) * t + 1; 65 R[i] = i * t; 66 } 67 if(R[t] < n) t++, L[t] = R[t - 1] + 1, R[t] = n; 68 for(int i = 1; i <= t; ++i) 69 for(int j = L[i]; j <= R[i]; ++j) { 70 pos[j] = i; 71 sum[i] += a[j]; 72 } 73 for(int i = 1; i <= n; ++i) { 74 opt = read(), l = read(), r = read(), d = read(); 75 if(!opt) change(l, r, d); 76 else cout << ask(l, r, d + 1) << '\n'; 77 } 78 return 0; 79 }
分塊入門5:
給出一個長為 n 的數列 a1…an,以及 n 個操作,操作涉及區間開方,區間求和。
對於區間開方,我們可以想到,對於一個區間的數,經過數次開方后,他們會變為0或1,所以采取一種分塊優化的暴力做法,只要每個整塊暴力開方后,記錄一下元素是否都變成了 0 / 1,區間修改時跳過那些全為 0 / 1 的塊即可。
1 #include<bits/stdc++.h>
2 #define ll long long
3 using namespace std; 4 const int maxn = 50086; 5 int n, a[maxn]; 6 int sum[maxn], pos[maxn]; 7 int L[maxn], R[maxn]; 8 int t; 9 bool vis[maxn]; 10 int opt, l, r, c; 11
12 inline int read() { 13 int x = 0, y = 1; 14 char ch = getchar(); 15 while(!isdigit(ch)) { 16 if(ch == '-') y = -1; 17 ch = getchar(); 18 } 19 while(isdigit(ch)) { 20 x = (x << 1) + (x << 3) + ch - '0'; 21 ch = getchar(); 22 } 23 return x * y; 24 } 25
26 inline void check_sqrt(int x) { 27 if(vis[x]) return; 28 vis[x] = 1; 29 sum[x] = 0; 30 for(int i = L[x]; i <= R[x]; ++i) { 31 a[i] = sqrt(a[i]); 32 sum[x] += a[i]; 33 if(a[i] > 1) vis[x] = 0; 34 } 35 } 36
37 inline void change(int l, int r) { 38 int p = pos[l], q = pos[r]; 39 if(p == q) { 40 for(int i = l; i <= r; ++i) { 41 sum[p] -= a[i]; 42 a[i] = sqrt(a[i]); 43 sum[p] += a[i]; 44 } 45 } 46 else { 47 for(int i = p + 1; i <= q - 1; ++i) 48 check_sqrt(i); 49 for(int i = l; i <= R[p]; ++i) { 50 sum[p] -= a[i]; 51 a[i] = sqrt(a[i]); 52 sum[p] += a[i]; 53 } 54 for(int i = L[q]; i <= r; ++i) { 55 sum[q] -= a[i]; 56 a[i] = sqrt(a[i]); 57 sum[q] += a[i]; 58 } 59 } 60 } 61
62 inline int ask(int l, int r) { 63 int ans = 0; 64 int p = pos[l], q = pos[r]; 65 if(p == q) for(int i = l; i <= r; ++i) ans += a[i]; 66 else { 67 for(int i = p + 1; i <= q - 1; ++i) ans += sum[i]; 68 for(int i = l; i <= R[p]; ++i) ans += a[i]; 69 for(int i = L[q]; i <= r; ++i) ans += a[i]; 70 } 71 return ans; 72 } 73
74 int main() { 75 memset(vis, false, sizeof(vis)); 76 n = read(); t = sqrt(n); 77 for(int i = 1; i <= n; ++i) a[i] = read(); 78 for(int i = 1; i <= t; ++i) { 79 L[i] = (i - 1) * t + 1; 80 R[i] = i * t; 81 } 82 if(R[t] < n) t++, L[t] = R[t - 1] + 1, R[t] = n; 83 for(int i = 1; i <= t; ++i) 84 for(int j = L[i]; j <= R[i]; ++j) { 85 sum[i] += a[j]; 86 pos[j] = i; 87 } 88 for(int i = 1; i <= n; ++i) { 89 opt = read(), l = read(), r = read(), c = read(); 90 if(!opt) change(l, r); 91 else cout << ask(l, r) << '\n'; 92 } 93 return 0; 94 }
分塊入門6:
給出一個長為 n 的數列,以及 n 個操作,操作涉及單點插入,單點詢問,數據隨機生成。
1 #include<bits/stdc++.h>
2 using namespace std; 3 const int maxn = 1e5 + 10; 4 const int maxq = 1e3 + 10; 5 struct enkidu { 6 int s, t; 7 }; 8 int n, a[maxn]; 9 int s[maxn << 1]; 10 int k, m;
11 int opt, l, r, c; 12 vector<int> e[maxq]; 13
14 inline int read() { 15 int x = 0, y = 1; 16 char ch = getchar(); 17 while(!isdigit(ch)) { 18 if(ch == '-') y = -1; 19 ch = getchar(); 20 } 21 while(isdigit(ch)) { 22 x = (x << 1) + (x << 3) + ch - '0'; 23 ch = getchar(); 24 } 25 return x * y; 26 } 27
28 inline enkidu query(int x) { 29 int i = 1; 30 while(x > (int)e[i].size()) 31 x -= (int)e[i].size(), i++; 32 return (enkidu){i, x - 1}; 33 } 34
35 inline void rebuild() {
36 int top = 0; 37 for(int i = 1; i <= m; ++i) { 38 int z = e[i].size(); 39 for(int j = 0; j < z; ++j) 40 s[++top] = e[i][j]; 41 e[i].clear(); 42 } 43 k = sqrt(top), m = (top - 1) / k + 1; 44 for(int i = 1; i <= top; ++i) 45 e[(i - 1) / k + 1].push_back(s[i]); 46 } 47
48 inline void change(int l, int r) { 49 enkidu x = query(l); 50 int s = x.s, t = x.t; 51 e[s].insert(e[s].begin() + t, r); 52 if((int)e[s].size() > 20 * k) 53 rebuild(); 54 } 55
56 int main() { 57 n = read(); 58 for(int i = 1; i <= n; ++i) a[i] = read(); 59 k = sqrt(n), m = (n - 1) / k + 1;
60 for(int i = 1; i <= n; ++i) 61 e[(i - 1) / k + 1].push_back(a[i]); 62 for(int i = 1; i <= n; ++i) { 63 opt = read(), l = read(), r = read(), c = read(); 64 if(!opt) change(l, r); 65 else { 66 enkidu x = query(r); 67 int s = x.s, t = x.t; 68 cout << e[s][t] << '\n'; 69 } 70 } 71 return 0; 72 }
分塊入門7:
給出一個長為 n 的數列,以及 n 個操作,操作涉及區間乘法,區間加法,單點詢問。
比較難處理的是乘法與加法的優先級問題,對於加法,我們直接累加,對於乘法,我們在加法標記和乘法標記上都乘上要乘的數就可以保證優先級的正確性(這是很顯然的)
1 #include<bits/stdc++.h>
2 #define ll long long
3 using namespace std; 4 const int maxn = 100086; 5 const int mod = 10007; 6 int n, t; 7 int a[maxn]; 8 int add[maxn], mul[maxn]; 9 int pos[maxn]; 10 int opt, l, r, c; 11
12 inline ll read() { 13 ll x = 0, y = 1; 14 char ch = getchar(); 15 while(!isdigit(ch)) { 16 if(ch == '-') y = -1; 17 ch = getchar(); 18 } 19 while(isdigit(ch)) { 20 x = (x << 1) + (x << 3) + ch - '0'; 21 ch = getchar(); 22 } 23 return x * y; 24 } 25
26 inline void reset(int x) { 27 for(int i = (x - 1) * t + 1; i <= min(n, x * t); ++i) 28 a[i] = (a[i] * mul[x] + add[x]) % mod; 29 add[x] = 0, mul[x] = 1; 30 } 31
32 inline void change(int flag, int l, int r, int c) { 33 reset(pos[l]); 34 for(int i = l; i <= min(pos[l] * t, r); ++i) { 35 if(!flag) a[i] += c; 36 else a[i] *= c; 37 a[i] %= mod; 38 } 39 if(pos[l] != pos[r]) { 40 reset(pos[r]); 41 for(int i = (pos[r] - 1) * t + 1; i <= r; ++i) { 42 if(!flag) a[i] += c; 43 else a[i] *= c; 44 a[i] %= mod; 45 } 46 } 47 for(int i = pos[l] + 1; i <= pos[r] - 1; ++i) { 48 if(!flag) add[i] = (add[i] + c) % mod; 49 else if(flag) { 50 add[i] = add[i] * c % mod; 51 mul[i] = mul[i] * c % mod; 52 } 53 } 54 } 55
56 int main() { 57 n = read(); t = sqrt(n); 58 for(int i = 1; i <= n; ++i) a[i] = read(); 59 for(int i = 1; i <= n; ++i) 60 pos[i] = (i - 1) / t + 1; 61 for(int i = 1; i <= pos[n]; ++i) mul[i] = 1; 62 for(int i = 1; i <= n; ++i) { 63 opt = read(), l = read(), r = read(), c = read(); 64 if(opt == 2) cout << (a[r] * mul[pos[r]] + add[pos[r]]) % mod << '\n'; 65 else change(opt, l, r, c); 66 } 67 return 0; 68 }
分塊入門8:
給出一個長為 n 的數列,以及 n 個操作,操作涉及區間詢問等於一個數 c 的元素,並將這個區間的所有元素改為 c。
區間修改和詢問操作,遇到以后建議線段樹(逃)
1 #include<bits/stdc++.h>
2 using namespace std; 3 const int maxn = 100086; 4 int n, t; 5 int a[maxn]; 6 int pos[maxn], tag[maxn]; 7 int l, r, c; 8
9 inline int read() { 10 int x = 0, y = 1; 11 char ch = getchar(); 12 while(!isdigit(ch)) { 13 if(ch == '-') y = -1; 14 ch = getchar(); 15 } 16 while(isdigit(ch)) { 17 x = (x << 1) + (x << 3) + ch - '0'; 18 ch = getchar(); 19 } 20 return x * y; 21 } 22
23 inline void reset(int x) { 24 if(tag[x] == -1) return; 25 for(int i = (x - 1) * t + 1; i <= x * t; ++i) 26 a[i] = tag[x]; 27 tag[x] = -1; 28 } 29
30 inline int sovle(int l, int r, int c) { 31 int ans = 0; 32 reset(pos[l]); 33 for(int i = l; i <= min(pos[l] * t, r); ++i) { 34 if(a[i] != c) a[i] = c; 35 else ans++; 36 } 37 if(pos[l] != pos[r]) { 38 reset(pos[r]); 39 for(int i = (pos[r] - 1) * t + 1; i <= r; ++i) { 40 if(a[i] != c) a[i] = c; 41 else ans++; 42 } 43 } 44 for(int i = pos[l] + 1; i <= pos[r] - 1; ++i) { 45 if(tag[i] != -1) { 46 if(tag[i] != c) tag[i] = c; 47 else ans += t; 48 } 49 else { 50 for(int j = (i - 1) * t + 1; j <= i * t; ++j) { 51 if(a[j] != c) a[j] = c; 52 else ans++; 53 } 54 tag[i] = c; 55 } 56 } 57 return ans; 58 } 59
60 int main() { 61 memset(tag, -1, sizeof(tag)); 62 n = read(); t = sqrt(n); 63 for(int i = 1; i <= n; ++i) a[i] = read(); 64 for(int i = 1; i <= n; ++i) 65 pos[i] = (i - 1) / t + 1; 66 for(int i = 1; i <= n; ++i) { 67 l = read(), r = read(), c = read(); 68 cout << sovle(l, r, c) << '\n'; 69 } 70 return 0; 71 }
分塊入門9:
給出一個長為 n 的數列,以及 n 個操作,操作涉及詢問區間的最小眾數。
分塊最經典的區間眾數操作,因為不具有區間相加性,無法使用線段樹或是樹狀數組完成。
當然學了莫隊就很簡單,不會莫隊(比如我),就老老實實分塊吧....
首先先離散化處理一下
接着枚舉預處理出任意一個區間[l, r]的眾數,也就是任意一個塊的左端點到其他塊的右端點的眾數(看了代碼就懂了系列),非常顯然的使用數組記錄每個數出現過的次數,然后再枚舉一遍,求出眾數
對於每一次詢問的區間[l, r],我們可以和明顯的發現,大區間的眾數存在於大區間內完整的塊里或是左右兩端不完整的塊里
對於完整的塊里,顯然我們已經與處理過了,直接詢問即可,所以我們需要快速求出不完整的塊中的區間眾數
然后可以發現我們上面進行了離散化,所以我們在離散時動動手腳就很好辦了,對於每數,使用vector存每一次出現的位置,
然后把這個數存在的左端點和右端點二分再相減就可以求出這個數在區間里出現的次數,也就是求區間內第一個大於這個數的位置(upper_bound)和第一個大於等於這個數的位置(lower_bound)
再相減即可
剩下的直接看代碼即可
1 #include<bits/stdc++.h>
2 using namespace std; 3 const int maxn = 50005; 4 int f[505][505]; 5 int a[maxn], pos[maxn]; 6 int val[maxn], cnt[maxn]; 7 int n, l, r, t, id; 8 map<int, int> mp; 9 vector<int> v[maxn]; 10
11 inline int read() { 12 int x = 0, y = 1; 13 char ch = getchar(); 14 while(!isdigit(ch)) { 15 if(ch == '-') y = -1; 16 ch = getchar(); 17 } 18 while(isdigit(ch)) { 19 x = (x << 1) + (x << 3) + ch - '0'; 20 ch = getchar(); 21 } 22 return x * y; 23 } 24
25 inline void pre(int x) { 26 memset(cnt, 0, sizeof(cnt)); 27 int maxx = 0 , ans = 0; 28 for(int i = (x - 1) * t + 1; i <= n; ++i) { 29 cnt[a[i]]++; 30 int p = pos[i]; 31 if(cnt[a[i]] > maxx || ((cnt[a[i]] == maxx) && val[a[i]] < val[ans])) 32 ans = a[i], maxx = cnt[a[i]]; 33 f[x][p] = ans; 34 } 35 } 36
37 inline int query_cnt(int l, int r, int x) { 38 int k = upper_bound(v[x].begin(), v[x].end(), r) - lower_bound(v[x].begin(), v[x].end(), l); 39 return k; 40 } 41
42 inline int query(int l, int r) { 43 int ans, maxx = -1000000; 44 ans = f[pos[l] + 1][pos[r] - 1]; 45 maxx = query_cnt(l, r, ans); 46 for(int i = l; i <= min(pos[l] * t, r); ++i) { 47 int k = query_cnt(l, r, a[i]); 48 if(k > maxx || (k == maxx && val[ans] > val[a[i]])) 49 ans = a[i], maxx = k; 50 } 51 if(pos[l] != pos[r]) { 52 for(int i = (pos[r] - 1) * t + 1; i <= r; ++i) { 53 int k = query_cnt(l, r, a[i]); 54 if(k > maxx || (k == maxx && val[ans] > val[a[i]])) 55 ans = a[i], maxx = k; 56 } 57 } 58 return ans; 59 } 60
61 int main() { 62 n = read(); t = 200; 63 for(int i = 1; i <= n; ++i) { 64 a[i] = read(); 65 if(!mp[a[i]]) { 66 mp[a[i]] = ++id; 67 val[id] = a[i]; 68 } 69 a[i] = mp[a[i]]; 70 v[a[i]].push_back(i); 71 } 72 for(int i = 1; i <= n; ++i) pos[i] = (i - 1) / t + 1; 73 for(int i = 1; i <= pos[n]; ++i) pre(i); 74 for(int i = 1; i <= n; ++i) { 75 l = read(), r = read(); 76 if(l > r) swap(l, r); 77 cout << val[query(l, r)] << '\n'; 78 } 79 return 0; 80 }
已下是毒瘤題!代碼毒瘤!
題面:給定一個長度為n的序列,求有多少個區間[l, r]滿足:區間內每個數都出現奇數次
解決的主要思想是:
給每個數x賦上一個隨機權值Hx,這樣就能把問題轉化成:
求有多少個區間,使得區間內的所有數權值異或和等於區間內出現過的數權值異或和
思考為什么會有這樣的轉化:
首先我們可以知道,對於有着相同權值的數,若出現了多次肯定會不斷抵消。
當一個數x出現了奇數次時,將區間內所有的x異或起來,得到的仍是x,這樣也就是說,若區間內的所有數異或起來等於區間內出現過的
數的權值異或和
就說明這個區間內每個數都出現了奇數次
接下來思考基於這個思想的具體算法:
記prei表示i這個數上一次出現的位置。
那么問題轉化為:枚舉區間右端點𝑟,每次兩個操作:
1. 給區間 [1, prear]異或一個數;
2. 詢問[1, r]中為0的數的個數
為什么可以這樣呢,注意我們的prei的含義:它表示的是這個數上次出現的位置
我們考慮我們將要插入一個數x,若[1, prear]之間異或上這個x的權值Hx,然后里面部分數變成了0,說明了什么:
說明了在區間[1, prear]中,x這個數出現了奇數次,這樣我們枚舉右端點r,然后每次這樣操作,再查詢0的個數,用一個變量ans累加答案
最后得到的就是有多少個區間滿足每個數出現了奇數次了
然后考慮代碼實現:
因為數據范圍可能較大,所以我們素質離散一下
同時為了避免重復,我們rand出的權值可能會很大,所以我們要注意使用unsigned long long
對於區間異或我們可以打標記,區間詢問即為統計值為標記的數的個數。
同時關於哪個數賦了什么權值,我們也需要存儲,比如說我們可以用STL的map來實現
但是眾所周知map有O(logn)的復雜度,而且即便我們如此取權值也可能會有重復
因此我們可以選擇毒瘤hash
遇到不完整的塊的時候,直接暴力處理過后把塊的信息重新統計即可
這樣復雜度就是O(n√n)
但是口胡算法並沒什么用處,代碼實現還是相當的毒瘤的,某個寫std的人如是說道:“哎我當初在干什么”“哎我都寫了什么”
所以具體細節還是代碼見吧
1 #include<bits/stdc++.h>
2 #define ll long long
3 #define uint unsigned int
4 #define ull unsigned long long
5 using namespace std; 6 const int maxn = 2e5 + 10; 7 const int kuai = 510; 8 const int MOD = 2339;//uss it to hash
9 struct shiki { 10 int lin[MOD + 5], net[MOD + 5], len; 11 int hid[MOD + 5], cnt[MOD + 5], id; 12 ull to[MOD + 5]; 13 inline void clear_() { 14 len = id = 0; 15 memset(lin, 0, sizeof(lin)); 16 memset(hid, 0, sizeof(hid)); 17 } 18 inline void insert(int xx, ull yy, int v) { 19 to[++len] = yy; 20 net[len] = lin[xx]; 21 cnt[len] = v; 22 lin[xx] = len; 23 } 24 //最初沒有插入任何數,對每個單獨的數組成的只有一個區間的數來說,出現次數都是奇數次,因此賦上(r-l+1)的初值 25 //cnt[i]表示對於某個數出現了多少次,下標i對應在鏈中的位置(大概),參考鄰接表
26 inline void init(int L, int R) {//最初
27 insert(0, 0, R - L + 1); 28 } 29 inline void reset() {//reset:重置, 在將塊重構時,因為直接memset的復雜度可能是錯誤的,所以每次把tot歸零
30 id++, len = 0;//同時用一個id表示最新版本是哪個版本以方便把后續節點安排上
31 } 32 inline int place(int x) {//hid數組表示對於一個取模后的數hx,它是哪一個版本的,方便把它更新掉
33 if(hid[x] == id) return lin[x];//若當前值在新的表中出現過,return head[x]
34 else { 35 hid[x] = id;//else 把當前值插入新的hash表中
36 return lin[x] = 0; 37 } 38 } 39 inline void add(ull x) { 40 int hx = x % MOD; 41 for(int i = place(hx); i; i = net[i]) 42 if(to[i] == x) {//表示在這個新的hash塊里是否存在和取模后的x值相等的,若相等,則說明插入后to[i]的數量++
43 cnt[i]++;//則這個數的數量++
44 return; 45 } 46 insert(hx, x, 1);//若未找到,將x插入
47 } 48 inline int query_cnt(ull x) {//詢問針對完整的塊tag表示這些塊異或了數次后的值
49 int hx = x % MOD; 50 for(int i = place(hx); i; i = net[i])//搜索hash值為hx的數,若此前有和這個值相等的數,則說明異或上x后,可以得到0,也就是說在x前,這個數出現了奇數次
51 if(to[i] == x) return cnt[i];//則符合要求,輸出這個數的個數
52 return 0; 53 } 54 }hash[kuai]; 55 int a[maxn]; 56 ull H[maxn];//H數組表示賦上的隨機權值
57 int pos[maxn], L[maxn], R[maxn]; 58 int b[maxn], c[maxn], tot = 0; 59 ull tag[kuai];//對於整個的塊表示異或了數次后的值
60 ull cag[maxn];//對於非整塊的表示異或了數次后的值
61 int n, m, t; 62 int last[maxn];//數a[i]上一次出現的位置
63
64 inline int read() { 65 int x = 0, y = 1; 66 char ch = getchar(); 67 while(!isdigit(ch)) { 68 if(ch == '-') y = -1; 69 ch = getchar(); 70 } 71 while(isdigit(ch)) { 72 x = (x << 1) + (x << 3) + ch - '0'; 73 ch = getchar(); 74 } 75 return x * y; 76 } 77
78 inline int query_lisan(int x) { 79 return lower_bound(b + 1, b + tot + 1, x) - b; 80 } 81
82 inline ull irand() { 83 ull res = 0; 84 for(int i = 1; i <= 5; ++i) res = (((res + rand()) * rand()) << 15) + rand(); 85 return res; 86 } 87
88 inline void deal_xor(int x, ull hx) { 89 if(!x) return; 90 int p = pos[x]; 91 for(int i = 1; i < p; ++i) tag[i] ^= hx; 92 hash[p].reset(); 93 for(int i = L[p]; i <= x; ++i) { 94 cag[i] ^= hx; 95 hash[p].add(cag[i]); 96 } 97 for(int i = x + 1; i <= R[p]; ++i) hash[p].add(cag[i]); 98 } 99
100 inline int query(int x) { 101 int p = pos[x], res = 0; 102 for(int i = 1; i < p; ++i) res += hash[i].query_cnt(tag[i]); 103 for(int i = L[p]; i <= x; ++i) 104 if(cag[i] == tag[p]) res++; 105 return res; 106 } 107
108 int main() { 109 n = read(); t = sqrt(n); 110 for(int i = 1; i <= n; ++i) { 111 a[i] = read(); 112 c[i] = a[i]; 113 } 114 //離散化
115 sort(c + 1, c + n + 1); 116 for(int i = 1; i <= n; ++i) 117 if(i == 1 || c[i - 1] != c[i]) 118 b[++tot] = c[i]; 119 for(int i = 1; i <= n; ++i) { 120 int pal = query_lisan(a[i]); 121 c[i] = pal;//c數組為離散后的a數組
122 } 123
124 //check 125 // for(int i = 1; i <= n; ++i) cout << c[i] << ' '; 126 // cout << '\n'; 127
128
129 //分塊
130 for(int i = 1; i <= t; ++i) { 131 L[i] = (i - 1) * t + 1; 132 R[i] = i * t; 133 } 134 if(R[t] < n) t++, L[t] = R[t - 1] + 1, R[t] = n; 135 for(int i = 1; i <= t; ++i) 136 for(int j = L[i]; j <= R[i]; ++j) 137 pos[j] = i; 138 ll ans = 0; 139 //start to answer questions
140 for(int i = 1; i <= tot; ++i) H[i] = irand();//賦一個隨機權值
141 for(int i = 1; i <= t; ++i) hash[i].clear_(), hash[i].init(L[i], R[i]); 142 for(int i = 1; i <= n; ++i) { 143 int pre = last[c[i]]; last[c[i]] = i; 144 deal_xor(pre, H[c[i]]); 145 ans += query(i); 146 } 147 printf("%lld\n", ans); 148 return 0; 149 }
