标签:sequence 数加 大于 指针 $1 直接 return 比较 就是
似乎正解很简单(就是一个很简单的容斥),然而我就是要写麻烦的2333
首先考虑到能够交换两个二进制位,那么一个数产生的贡献就只和它的二进制的$1$的个数相关,我们将$a$数组转化为$b$数组,其中$b_i$为$a_i$的二进制位数,而$sum_i$为$b_i$的前缀和。
然后我们考虑统计答案。显然我们需要统计区间和为偶数的区间,也就是$\sum\limits_i \sum\limits_{j=0}^{i-1} [sum_i \% 2 == sum_j \% 2]$。我们需要求一个前缀和:$cnt_{i,0/1}$表示$sum_1$到$sum_i$中$mod \, 2 = 0/1$的数量,这样就可以$O(64N)$计算上面的式子了。但是我们少考虑了一个问题:如果区间满足和为偶数,但是其中有一个数的二进制位数很大,以至于其他的二进制数加起来都抵不过它(即$\max\limits_{k=i+1} ^ j {b_k} \times 2 > sum_j-sum_i$),意味着这个区间不合法。
那么就有两种解决办法:
①因为与最大值相关,所以考虑最大值分治。设$right_{i,j}$表示使得$sum_x - sum_{i-1} > 2 \times j$的最小的$x$,不存在则为$N+1$,又设$left_{i,j}$表示使得$sum_i - sum_{x - 1} > 2 \times j$的最大的$x$,用双指针预处理这两个数组。每一次在当前解决的区间上找到最大值,分治下去,对于当前的这一段区间,找大小比较小的那一段,利用$left,right,cnt$可以$O(1)$得到与$i$匹配的左/右端点的数量。复杂度$O(N(logN+64))$,时空无一被爆踩,唯一的优越性可能就在于可以做$a_i=0$的情况吧(强行安慰自己qwq)
1 #include<bits/stdc++.h> 2 #define ull unsigned long long 3 using namespace std; 4 5 inline ull read(){ 6 ull a = 0; 7 char c = getchar(); 8 while(!isdigit(c)) 9 c = getchar(); 10 while(isdigit(c)){ 11 a = (a << 3) + (a << 1) + (c ^ ‘0‘); 12 c = getchar(); 13 } 14 return a; 15 } 16 17 const int MAXN = 3e5 + 10; 18 int num[MAXN] , rig[MAXN][65] , lef[MAXN][65] , cnt[MAXN][2] , sum[MAXN] , ST[21][MAXN] , logg2[MAXN] , N; 19 long long ans; 20 21 inline int cmp(int a , int b){ 22 return num[a] > num[b] ? a : b; 23 } 24 25 void init_st(){ 26 for(int i = 2 ; i <= N ; ++i) 27 logg2[i] = logg2[i >> 1] + 1; 28 for(int i = 1 ; 1 << i <= N ; ++i) 29 for(int j = 1 ; j + (1 << i) - 1 <= N ; ++j) 30 ST[i][j] = cmp(ST[i - 1][j] , ST[i - 1][j + (1 << (i - 1))]); 31 } 32 33 inline int query(int x , int y){ 34 int t = logg2[y - x + 1]; 35 return cmp(ST[t][x] , ST[t][y - (1 << t) + 1]); 36 } 37 38 void solve(int l , int r){ 39 if(l > r) 40 return; 41 if(l == r){ 42 ans += num[l] == 0; 43 return; 44 } 45 int k = query(l , r); 46 //cout << l << ‘ ‘ << r << ‘ ‘ << k << endl; 47 solve(l , k - 1); 48 solve(k + 1 , r); 49 if(k - l < r - k) 50 for(int i = k ; i >= l ; --i) 51 ans += cnt[r][sum[i - 1] & 1] - cnt[min(max(k - 1 , rig[i][num[k]] - 1) , r)][sum[i - 1] & 1]; 52 else 53 for(int i = k ; i <= r ; ++i) 54 if(lef[i][num[k]] >= l) 55 ans += cnt[min(k - 1 , lef[i][num[k]] - 1)][sum[i] & 1] - (l == 1 ? 0 : cnt[l - 2][sum[i] & 1]); 56 //cout << l << ‘ ‘ << r << ‘ ‘ << ans << endl; 57 } 58 59 int main(){ 60 N = read(); 61 cnt[0][0] = 1; 62 for(int i = 1 ; i <= N ; ++i){ 63 ull a = read(); 64 while(a){ 65 if(a & 1) 66 ++num[i]; 67 a >>= 1; 68 } 69 sum[i] = sum[i - 1] + num[i]; 70 cnt[i][0] = cnt[i - 1][0] + !(sum[i] & 1); 71 cnt[i][1] = cnt[i - 1][1] + (sum[i] & 1); 72 ST[0][i] = i; 73 //cout << num[i] << ‘ ‘; 74 } 75 for(int i = 0 ; i <= 64 ; ++i){ 76 int p = 0; 77 for(int j = 1 ; j <= N ; ++j) 78 while(p < j && sum[j] - sum[p] >= i << 1) 79 rig[++p][i] = j; 80 while(p < N) 81 rig[++p][i] = N + 1; 82 for(int j = N ; j ; --j) 83 while(p >= j && sum[p] - sum[j - 1] >= i << 1) 84 lef[p--][i] = j; 85 } 86 init_st(); 87 //cout << endl; 88 solve(1 , N); 89 cout << ans; 90 return 0; 91 }
②可以发现最大的元素不会大于$64$。那么对于每一个点,当它为右端点时,实际上最多只会有$64$个左端点有可能不合法,暴力把这$64$个端点扫一遍就行了。复杂度$O(64N)$
这个代码直接去看$Tutorial$吧
CF1030E Vasya and Good Sequences 最大值分治/容斥
标签:sequence 数加 大于 指针 $1 直接 return 比较 就是
原文地址:https://www.cnblogs.com/Itst/p/10089568.html