(ST表+二分+前綴和)CSU 1879 - Hack Protection

題意:ios

給定一個序列,求異或和按位與和相同的區間有幾個。算法

異或和:n個數異或起來。按位與和相似。數組

 

分析:ui

這纔是神題,基礎算法大雜燴。spa

問大佬這題的時候,人家只說很不難啊。。code

只能說本身太菜。blog

因爲詢問區間個數,天然要快速知道某一個區間的異或和按位與和。string

異或和很簡單,利用他的性質,直接求前綴和便可。it

可是按位與沒有和加法異或相似的性質,沒法直接求出。io

可是利用以前的知識能夠將全部結果預處理出來,並且只要nlogn的複雜度。

就是以前搞RMQ的ST表,很適合離線查詢。

幾乎不用動的把 min或max換成&,就能獲得O(1)查詢區間按位與和的方法。

此時,依然無從下手。

咱們還須要知道一個性質,就是說按位與是單調遞減的!

由於二進制表示中,1不會增多,只會減小,再確切一點在32位int整數中只會減小32次,最多!

因此最後只會出來相似999998877444422222221110000000這樣的值。

也就是說固定每一位左端點,從左端點按位與到最後一位都是出現想上面這樣相似的數列。

這時咱們就能發現

假設當前固定左端點爲L,第一個9的開頭是R,9的結尾是W,i爲從R到W。

異或前綴和用pre數組表示。

那麼那麼若是要是異或和和按位與相等。

必需要知足pre[i]^pre[L-1]==9

咱們須要統計的是知足這樣條件的i的數量。

可是顯然不能使用暴力的方式。

繼續利用異或的性質,a^b=c  ==> a=c^b

咱們能夠知道pre[i]==9^pre[L-1]

此時咱們只要找前綴和裏有幾個是知足這個條件的。

這裏又是一個很是牛逼的方法,直接用map<int,vector<int>>記錄每個前綴異或和的全部下標。

還記得當年的有序數列計數操做麼?

upper_bound - lower_bound。。。。

最終複雜度O(n*log(ai)*log(n))

到此,全部題解都結束了,又回憶了一次,只想說好牛逼。。

絕對好題。。

 

代碼:

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 #include <map>
 5 #include <iostream>
 6 #include <vector>
 7 
 8 
 9 
10 using namespace std;
11 
12 const int inf = 0x3f3f3f3f;
13 const int maxn = 100010;
14 
15 #define fi first
16 #define se second
17 typedef long long ll;
18 typedef pair<int, int> pii;
19 
20 int n;
21 int num[maxn];
22 int pre[maxn];
23 
24 int st[maxn];
25 int dp[maxn][20];
26 
27 map<int, vector<int> > xo;
28 
29 void init() {
30     pre[0] = 0;
31     xo.clear();
32 }
33 
34 void Build_ST() {
35     st[0] = -1;
36     for(int i = 1; i <= n; i++) {
37         st[i] = ((i & (i - 1)) == 0) ? st[i - 1] + 1 : st[i - 1];
38         dp[i][0] = num[i];
39     }
40     for(int  j = 1; j <= st[n]; j++) {
41         for(int i = 1; i + (1 << (j - 1))  <= n; i++) {
42             dp[i][j] = dp[i][j - 1] & dp[i + (1 << (j - 1))][j - 1];
43         }
44     }
45 }
46 
47 int ga(int l, int r) {
48     int k = st[r - l + 1];
49     return dp[l][k] & dp[r - (1 << k) + 1][k];
50 }
51 
52 int gr(int zl, int l, int b) {
53     int left = l, right = n;
54     int w = l + 1;
55     while(left <= right) {
56         int mid = (left + right) >> 1;
57         int pp = ga(zl, mid);
58         if(pp == b) {
59             left = mid + 1;
60             w = mid;
61         } else {
62             right = mid - 1;
63         }
64     }
65     return w;
66 }
67 
68 
69 int main() {
70 //    freopen("hack.in", "r", stdin);
71 //    freopen("hack.out", "w", stdout);
72     scanf("%d", &n);
73     init();
74     for(int i = 1; i <= n; i++) {
75         scanf("%d", &num[i]);
76         pre[i] = pre[i - 1] ^ num[i];
77         xo[pre[i]].push_back(i);
78     }
79     Build_ST();
80     ll ans = 0;
81     for(int i = 1; i <= n; i++) {
82         int w = i - 1;
83         int q = 2147483647;
84         for(int r = w; r < n; r = w) {
85             q &= num[r + 1];
86             w = gr(i, r + 1, q);
87             int d = pre[i - 1] ^ q;
88             ans += upper_bound(xo[d].begin(), xo[d].end(), w) - lower_bound(xo[d].begin(), xo[d].end(), r + 1);
89         }
90     }
91     printf("%lld\n", ans);
92     return 0;
93 
94 }
相關文章
相關標籤/搜索