1. 程式人生 > >CF1030E Vasya and Good Sequences 最大值分治/容斥

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

傳送門


似乎正解很簡單(就是一個很簡單的容斥),然而我就是要寫麻煩的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$吧