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

Posted tak_fate

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了(ST表+二分+前缀和)CSU 1879 - Hack Protection相关的知识,希望对你有一定的参考价值。

题意:

给定一个序列,求异或和按位与和相同的区间有几个。

异或和:n个数异或起来。按位与和类似。

 

分析:

这才是神题,基础算法大杂烩。

问大佬这题的时候,人家只说很不难啊。。

只能说自己太菜。

由于询问区间个数,自然要快速知道某一个区间的异或和按位与和。

异或和很简单,利用他的性质,直接求前缀和即可。

但是按位与没有和加法异或类似的性质,无法直接求出。

但是利用之前的知识可以将所有结果预处理出来,而且只要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 }

 

以上是关于(ST表+二分+前缀和)CSU 1879 - Hack Protection的主要内容,如果未能解决你的问题,请参考以下文章

CSU-2221 假装是区间众数(ST表模版题)

[LuoguP4094] [HEOI2016] [TJOI2016]字符串(二分答案+后缀数组+ST表+主席树)

2019 CCPC 网络赛第三题 K-th occurrence 后缀数组+划分树+ST表+二分

BZOJ 3230 相似子串 | 后缀数组 二分 ST表

POJ 3061 Subsequence ( 二分 || 尺取法 )

UVALive 6609 Minimal Subarray Length(RMQ-ST+二分)