码迷,mamicode.com
首页 > 其他好文 > 详细

CF1030E Vasya and Good Sequences 最大值分治/容斥

时间:2018-12-08 23:54:36      阅读:233      评论:0      收藏:0      [点我收藏+]

标签: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 }
被容斥包菜的分治程序qaq

②可以发现最大的元素不会大于$64$。那么对于每一个点,当它为右端点时,实际上最多只会有$64$个左端点有可能不合法,暴力把这$64$个端点扫一遍就行了。复杂度$O(64N)$

这个代码直接去看$Tutorial$吧

CF1030E Vasya and Good Sequences 最大值分治/容斥

标签:sequence   数加   大于   指针   $1   直接   return   比较   就是   

原文地址:https://www.cnblogs.com/Itst/p/10089568.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!