---
tags: VNOJ, THT, Medium Implementation, Implementation, DP, DP-digit, Bit Manipulation, Math, Duy_e
---
Tin học trẻ 2021 - Vòng sơ khảo - Bảng C - Tổng XOR
===
[https://oj.vnoi.info/problem/tht21_skc_xor](https://oj.vnoi.info/problem/tht21_skc_xor)
-----
###### Tags: `DP-digit`, `Bit Manipulation`, `Brute Forces`, `Math`
###### Contributor: @[SPyofgame](https://codeforces.com/profile/SPyofgame)
[TOC]
-----
### Hướng dẫn subtask 1
Ở subtask này, dễ thấy ta chỉ cần duyệt qua từng $X$ trong đoạn từ $L$ đến $R$ và tính kết quả với từng $X$. Sau đó ta duy trì một biến ```ans``` để lưu kết quả tốt nhất hiện tại.
Về phần đếm, ta duy trì một biến ```cnt``` để đếm số lượng hiện tại của ```ans```. Lúc này ta có hai trường hợp cần cập nhật:
***Trường hợp 1:*** Nếu kết quả của $X$ hiện tại ta đang xét lớn hơn ```ans```, ta cập nhật ```ans``` thành kết quả này và gán ```cnt = 1```.
***Trường hợp 2:*** Nếu kết quả của $X$ hiện tại ta đang xét bằng với ```ans```, ta tăng ```cnt``` lên một.
-----
### Code cho subtask 1
> Duyệt qua từng $X$: $O(R-L)$
> Với mỗi $X$ tính tổng các $b_{i}$: $O(N)$
> Tổng độ phức tạp là $O((R-L)*N)$
> **Time:** $O((R-L)*N)$
> **Space:** $O(N)$
> **Algo:** Brute force
> [color=lightgreen]
:::success
:::spoiler Code
```cpp=
#include <iostream>
using namespace std;
const long long N = 200;
int a[N], n, L, R, K;
int cal(int X){ // calculate the sum for X
int ans = 0;
for (int i = 1; i <= n; i ++)
ans += a[i]^X;
return ans;
}
int main(){
// Fast IO
ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0);
cin >> n >> L >> R >> K;
for (int i = 1; i <= n; i ++)
cin >> a[i];
int ans = 0, cnt = 0;
for (int X = L; X <= R; X ++)
if (X % K == 0) {
int ans_for_cur_X = cal(X);
if (ans_for_cur_X == ans)
cnt ++;
else if (ans_for_cur_X > ans)
ans = ans_for_cur_X, cnt = 1;
}
cout << ans << '\n' << cnt;
return 0;
}
```
:::
-----
### Hướng dẫn subtask 2
***Note***: ở đây dấu $\oplus$ có nghĩa là phép xor.
***Gợi ý 1:*** $L = 0, R = 2^{29} - 1, K = 1, 0 \leq a_{i} \le 2^{29} - 1$
***Gợi ý 2:*** Mọi cặp số nguyên không âm thuộc đoạn $[L..R]$ khi $xor$ lại sẽ cho kết quả không vượt quá $R$.
***Gợi ý 3:*** Các số cần xét đều thuộc dạng số nguyên có 29 bit !!
Đầu tiên, như hai gợi ý đầu, mọi $X$ thuộc $[L..R]$ đều hợp lệ (đều chia hết cho $K=1$). Và mọi giá trị $a_{i}$ đều thuộc $[L..R]$.
***Note:*** Vì đây là mấu chốt để giải cả những subtask sau nên mình sẽ nói rất kĩ.
Ở đây, ta cần biết về ý nghĩ của biểu diễn bit một số.
***Cụ thể ở đây là biểu diễn số thập phân sang nhị phân, nhưng ghi ngược chiều. (từ $4_{10} = 100_{2}$, ta biểu diễn thành $4_{10} = 001_{2}$ )***
Xét số nguyên $A = 4$ có biểu diễn bit là ```001```. Khi đánh số bit từ trái sang phải với chỉ số bắt đầu từ 0. Thì ta có thể tính được số $A$ theo cách sau:
> Xét từng chỉ số của biểu diễn nhị phân của $A$, ta sẽ đưa {bit hiện tại} nhân cho 2 lũy thừa {chỉ số} vào tổng.
#### Code giải thích cho cách chuyển
```cpp=
#include <string>
#include <iostream>
using namespace std;
long long Get_Dec(string Binary_presentation){
// Khi truyền A = 001 vào thì hàm sẽ trả về 4
int size = Binary_presentation.size();
int A = 0;
for (int i = 0; i < size; i ++){
int u;
if (Binary_presentation[i] == '1') u = 1; else u = 0;
A += u*(1 << i); // 1 << x = 2^x
}
return A;
}
int main(){
cout << Get_Dec("001") << '\n'; // output: 4
cout << Get_Dec("011") << '\n'; // output: 6
}
```
Vì $\forall a_{i}, 0 \le a_{i} \le 2^{29} - 1$, nên ta sẽ biểu diễn mỗi số trong dãy $a$ theo 29 bit nhị phân được đánh số từ $0$ đến $28$.
Khi biết được tính chất của bit và đã biểu diễn lại dãy $a$, ta có thể biểu diễn lại tổng dãy $a$ của đề như sau:
* Đầu tiên biểu diễn lại dãy $a$ theo ma trận nhị phân:
```
a = {1, 4, 6, 5}
a[0] : 1000
a[1] : 0010
a[2] : 0110
a[3] : 1010
```
* Đánh số cột từ 0, ta có $cnt[i]$ là tổng của cột $i$, hay có thể hiểu là số lượng bit $1$ có ở cột $i$
* Rõ ràng để tính tổng của cả mảng $a$ ta chỉ cần tính tổng của các $cnt[i] \times 2^{i}$
Phần còn lại là xử lý phép xor.
Một lần nữa biểu diễn dãy $a$ thành ma trận, lần này ta sẽ thêm cả $X$:
```
a = {1, 4, 6, 5}
X = 5: 1010
a[0] : 1000 |a[0] ⊕ X : 0010
a[1] : 0010 -> |a[1] ⊕ X : 1000
a[2] : 0110 |a[2] ⊕ X : 1100
a[3] : 1010 |a[3] ⊕ X : 0000
```
Ta có, $1 \oplus 1 = 0$ và $0 \oplus 1 = 1$
Lúc này, ta tính tổng của từng ${a_{i}}\oplus
{X}$, dựa vào ma trận trên có thể thấy khi ta biểu diễn từng $a_{i}\oplus
X$, nếu ở cột $i$ và bit của $i$ của $X$ là `1` thì mọi bit của cột $i$ sẽ lật đồng thời $cnt[i]$ lúc này cũng thay đổi, cụ thể $cnt[i]$ đổi thành $n - cnt[i]$
$L = 0, R = 2^{29} - 1, K = 1, a_{i} \le 2^{29} - 1$ nên thay vì tìm $X$ ta sẽ điều chỉnh từng bit trong $X$ sao cho ta có thể đạt được giá trị lớn nhất.
Về phần đếm xin trình bày trong code. Lúc này ta có thể giải như sau.
-----
### Code cho subtask 2
> Ở mỗi $i$ ta check từng bit của $a[i]$: $O(29)$ mỗi $i$
> Nên phần precalculate: $O(29 \times N)$
> Phần tính kết quả: $O(29)$
> Độ phức tạp tổng $O(29 \times N) + O(29) = O(29 \times N)$
>**Time:** $O(29 \times N)$
>**Space:** $O(N)$
>**Algo:** Bit Manipulation, Math
> [color=lightgreen]
:::success
:::spoiler Code
#include <iostream>
using namespace std;
const long long N = 1e5 + 5;
const long long lg = 29; // số lượng bit
int a[N], n, L, R, K;
int cnt[lg]; // tổng của cột trong bảng
int main(){
cin >> n >> L >> R >> K;
for (int i = 1; i <= n; i ++){
cin >> a[i];
for (int j = 0; j < lg; j ++){ // lúc này ta làm việc với bảng có đủ 29 cột và n hàng
if (a[i] & (1 << j)) // xét xem bit thứ j của a[i] là bit 1 hay 0.
cnt[j] ++; // bạn có thể viết hàm chuyển số sang string
}
}
long long ans = 0, number_of_X = 1;
// Luôn có thể tìm được X
// xét từ bit có bậc cao nhất về bit có bậc thấp nhất. (most significant bit)
for (int i = lg - 1; i >= 0; i --){
// xét 2 case:
// case 1: bit i là 0, không làm lật cột i
long long Case_1 = cnt[i]*(1ll << i); // 1 << i = 2 mũ i
// case 2: bit i là 1, làm lật toàn bộ cột i
long long Case_2 = (n - cnt[i])*(1ll << i);
if (Case_2 > Case_1)
ans += Case_2;
if (Case_1 > Case_2)
ans += Case_1;
if (Case_1 == Case_2)
ans += Case_1, number_of_X *= 2;
/*
Xét số X đang có suffix biểu diễn nhị phân là:
suf_X = ababab
với Trường hợp:
+ Case 1 > Case 2: số X của ta sẽ thành 0[ababab].
-> giữ nguyên số lượng
+ Case 2 > Case 1: số X của ta sẽ thành 1[ababab].
-> giữ nguyên số lượng
+ Case 1 = Case 2: số X của ta có thể là 0[ababab] hoặc 1[ababab].
-> số lượng nhân đôi
*/
}
cout << ans << '\n' << number_of_X;
return 0;
}
```
:::
> [color=lightgreen]
:::success
::: spoiler Hoặc code của @[SPyofgame](https://codeforces.com/profile/SPyofgame)
```cpp=
#include <iostream>
#include <cstring>
using namespace std;
typedef long long ll;
int n, l, r, k;
int cnt[30][2];
bool in_range(int x)
{
return (l <= x) && (x <= r);
}
ll dfs_sum(int x = 0, int bit = 29)
{
if (bit < 0) return 0;
int k = 1 << bit;
int lt = (cnt[bit][0] > 0) && in_range(x | k) ? k : 0;
int rt = (cnt[bit][1] > 0) ? k : 0;
ll lv = 1LL * lt * cnt[bit][0];
ll rv = 1LL * rt * cnt[bit][1];
// cout << bit << " " << k << " " << x << " | " << lt << " " << rt << " | " << lv << " " << rv << endl;
if (lv > rv)
return dfs_sum(x | lt, bit - 1) + lv;
else
return dfs_sum(x | rt, bit - 1) + rv;
}
int dfs_cnt()
{
int res = 1;
for (int bit = 0; bit < 30; ++bit)
res <<= (cnt[bit][0] == cnt[bit][1]);
return res;
}
int main()
{
ios::sync_with_stdio(NULL);
cin.tie(NULL);
memset(cnt, 0, sizeof(cnt));
cin >> n >> l >> r >> k;
for (int i = 1; i <= n; ++i)
{
int x;
cin >> x;
for (int bit = 0; bit < 30; ++bit)
++cnt[bit][x >> bit & 1];
}
cout << dfs_sum() << "\n";
cout << dfs_cnt() << "\n";
return 0;
}
```
:::
-----
### Hướng dẫn subtask 3
Điểm khác biệt duy nhất giữa subtask này với subtask 2 là giới hạn của $L$ và $R$ không còn cố định.
Để giải quyết bài này ta cần biết đến kĩ thuật `DP-digit`, cụ thể là biết cách xử lý đồng thời hai giới hạn của hàm `DP-digit`.
lúc này ta vẫn dùng ma trận để minh họa:
```
a = {1, 4, 6, 5}
X = 5: 1010
a[0] : 1000 |a[0] ⊕ X : 0010
a[1] : 0010 -> |a[1] ⊕ X : 1000
a[2] : 0110 |a[2] ⊕ X : 1100
a[3] : 1010 |a[3] ⊕ X : 0000
```
Một cách làm có thể như sau:
* Chuyển cả $L$ và $L$ về dạng nhị phân.
* Ta định nghĩa `dp[pos][is_greater][is_smaller]` là một cặp tổng lớn nhất cho đoạn prefix kết thúc ở pos của bảng trên khi từng phần tử xor cho $X$ nằm trong đoạn $[L..R]$ và số lượng $X$ đã tạo ra kết quả đó.
* Lúc này, `is_greater = true` nếu ta đang xét $X$ lớn hơn $L$
* Lúc này, `is_smaller = true` nếu ta đang xét $X$ bé hơn $R$
* Phần tính tổng và đếm cũng tương tự subtask 2.
* Khi này kết quả là sẽ là `dp[29][0][0]` với 29 là số lượng cột bit.
-----
### Code cho subtask 3
>***Tiền xử lý***: $O(N \times 29)$
>***Độ phức DP-digit***: $O(29 \times 2 \times 2)$
>***Độ phức tạp tổng***: $O(N \times 29 + 29 \times 2 \times 2) = O(N \times 29)$
>***Algo***: DP-digit, Bit Manipulation.
> [color=lightgreen]
:::success
:::spoiler Code
```cpp=
#include <iostream>
#include <vector>
#define ll long long
#define pii pair <ll, ll>
#define st first
#define nd second
using namespace std;
const long long lg = 30;
pii dp[lg][2][2];
ll n, k, L, R, cnt[lg];
vector <int> lower, upper; // hai cận
void convert(vector <int> &d, ll a){ //chuyển sang nhị phân
for (int i = 0; i < lg; i ++)
if (a & 1ll << i) d.push_back(1); else d.push_back(0);
}
pii cal(ll pos, bool is_greater, bool is_smaller){
if (pos < 0) return {0, 1};
pii &ans = dp[pos][is_greater][is_smaller];
if (ans.st != -1) return ans;
int upper_bound = upper[pos];
int lower_bound = lower[pos];
if (is_greater) lower_bound = 0;
if (is_smaller) upper_bound = 1;
ans = pii(-1, 0);
for (int i = lower_bound; i <= upper_bound; i ++){
bool new_g = is_greater;
bool new_s = is_smaller;
if (i > lower_bound) new_g = 1;
if (i < upper_bound) new_s = 1;
pii tmp = cal(pos - 1, new_g, new_s);
if (i == 1)
tmp.st += (n - cnt[pos])*(1ll << pos);
else
tmp.st += cnt[pos]*(1ll << pos);
if (tmp.st > ans.st)
ans = tmp;
else
if (ans.st == tmp.st)
ans.nd += tmp.nd;
}
// cout << ans.st << ' ' << ans.nd << '\n';
return ans;
}
int main(){
cin >> n >> L >> R >> k;
for (int i = 1, a; i <= n; i ++){
cin >> a;
for (int j = 0; j < lg; j ++)
if (a & 1ll << j) cnt[j] ++;
}
convert(lower, L);
convert(upper, R);
for (int i = 0; i < lg; i ++)
for (int j = 0; j < 2; j ++)
for (int t = 0; t < 2; t ++)
dp[i][j][t] = pii(-1, 0);
pii ans = cal(lg - 1, 0, 0);
cout << ans.st << '\n' << ans.nd;
}
```
:::
### Hướng dẫn subtask 4
Ở subtask 4 ta cũng xử lý dp gần như subtask 3, chỉ là ta thêm một chiều quản lý số dư của $X$ đang xét cho $K$.
Cụ thể ta sẽ có `dp[pos][is_greater][is_smaller][remain]` là một cặp tổng lớn nhất cho đoạn prefix kết thúc ở pos của bảng trên khi từng phần tử xor cho $X$ nằm trong đoạn $[L..R]$, với $X$ $mod$ $K=rem$, và số lượng $X$ đã tạo ra kết quả đó.
Đồng thời, ở đây có một `special subtask` khi mà $K$ lớn, cụ thể là $K \ge 10^{4}$. Vì ở đây số lượng $X$ có thể chia hết cho $K$ chỉ là $\frac{R - L}{k} \le 10^{5}$ nên ta hoàn toàn có thể brute từng $X$ và kết hợp với subtask 2 được cải tiến.
Từ đây, ta phân làm hai trường hợp:
- Nếu $K \le 10^{4}$: ta sẽ dùng DP-digit và kết quả sẽ là `dp[29][0][0][0]`.
- Nếu $K > 10^{4}$: ta sẽ duyệt qua từng $X$ chia hết cho $K$.
Phần code sẽ như sau.
----
### Code cho subtask 4
>***Tiền xử lý***: $O(N \times 29)$
>***Độ phức DP-digit***: $O(29 \times 2 \times 2)$
>***Độ phức tạp tổng***: $O(N \times 29 + 29 \times 2 \times 2) = O(N \times 29)$
>***Algo***: DP-digit, Bit Manipulation.
> [color=lightgreen]
:::success
:::spoiler Code
```cpp=
#include <iostream>
#include <vector>
#define ll long long
#define pii pair <ll, ll>
#define st first
#define nd second
using namespace std;
const long long lg = 30;
const long long Max_rem = 1e4;
const long long N = 1e5 + 5;
pii dp[lg][2][2][Max_rem];
ll n, k, L, R, cnt[lg];
vector <int> lower, upper; // hai cận
void convert(vector <int> &d, ll a){ //chuyển sang nhị phân
for (int i = 0; i < lg; i ++)
if (a & 1ll << i) d.push_back(1); else d.push_back(0);
}
pii cal(ll pos, bool is_greater, bool is_smaller, int rem){
if (pos < 0){
if (rem == 0)
return {0, 1};
return {-5e9, 0};
}
pii &ans = dp[pos][is_greater][is_smaller][rem];
if (ans.st != -1) return ans;
int upper_bound = upper[pos];
int lower_bound = lower[pos];
if (is_greater) lower_bound = 0;
if (is_smaller) upper_bound = 1;
ans = pii(-5e9, 0);
for (int i = lower_bound; i <= upper_bound; i ++){
bool new_g = is_greater;
bool new_s = is_smaller;
if (i > lower_bound) new_g = 1;
if (i < upper_bound) new_s = 1;
pii tmp = cal(pos - 1, new_g, new_s, (rem + i*(1ll << pos))%k);
if (i == 1)
tmp.st += (n - cnt[pos])*(1ll << pos);
else
tmp.st += cnt[pos]*(1ll << pos);
if (tmp.st > ans.st)
ans = tmp;
else
if (ans.st == tmp.st)
ans.nd += tmp.nd;
}
// cout << ans.st << ' ' << ans.nd << '\n';
return ans;
}
ll get(ll x){ //subtask 2 sau khi cải tiến với X cố định
ll ans = 0;
for (ll i = 0; i < lg; i ++)
if (x & 1ll << i)
ans += (n - cnt[i])*(1ll << i);
else
ans += cnt[i]*(1ll << i);
return ans;
}
void special_subtask(){
ll ans = 0, cnt = 0;
for (ll X = 0; X <= R; X += k){
if (X < L) continue;
ll ans_for_cur_X = get(X);
if (ans_for_cur_X == ans)
cnt ++;
else if (ans_for_cur_X > ans)
ans = ans_for_cur_X, cnt = 1;
}
cout << ans << '\n' << cnt;
}
int main(){
cin >> n >> L >> R >> k;
for (int i = 1, a; i <= n; i ++){
cin >> a;
for (int j = 0; j < lg; j ++)
if (a & 1ll << j) cnt[j] ++;
}
if (k > Max_rem){
special_subtask();
return 0;
}
convert(lower, L);
convert(upper, R);
for (int i = 0; i < lg; i ++)
for (int j = 0; j < 2; j ++)
for (int t = 0; t < 2; t ++)
for (int rem = 0; rem < Max_rem; rem ++)
dp[i][j][t][rem] = pii(-1, 0);
pii ans = cal(lg - 1, 0, 0, 0);
cout << ans.st << '\n' << ans.nd;
}
```
:::