題目網址: https://codeforces.com/contest/1322/problem/B
寫這篇博文是因爲第一次遇到這個解法,對於我這個算法小白來說還是很新穎的。
PS.做題的時候天真的以爲是O(n)的解法,並且可以用數學做。。。
題目
給定個數 , , … , ,
計算其兩兩之和的異或值。
分析
首先輸入大小以看就不可以直接求(廢話)
複雜度也就是壓在O(nlogn)左右
關鍵點
我們可以把答案回到二進制,然後對二進制的每一位分開求。
原理
首先我們定義 , 即 是兩數之和。
令 是答案, 是指從右往左答案的第 個二進制位(從0開始)
我們現在假設計算 ,我們已知所有 的第 位的01情況,那麼求 就是對它們做異或,也就等於計算 中第 位是1的個數的奇偶情況。
即如果1有偶數個,那麼答案對應的位數是0,反之則是1。(簡單的異或運算原理)
Subproblem
對於結果,計算第 位的值,即 。
(等價問題)計算兩數之和的第 位是 的數量。
對於這個等價問題,我們先思考一個簡單的情況:
假設現在有一個數 ,它的第位的是否位取決於它是否在 內。
現在把 擴大爲正數,我們可以得到其第位的情況與 $y’ = y \% 2^{i+1} $。
因此,對於每個 ,我們可以只考慮 $x \% 2^{i+1} $。
但我們希望能通過 直接計算出個數,而非計算 後求出解。
既然我們可以對 取模,自然也可以對 取模,這對加法並不會有影響。
因此,我們首先將 對 取模,令取模後的數爲 。
我們可以得到 ,
即有 。
我們發現 的範圍縮小了很多,這次我們不對 取模,
相反,我們可以直接對 劃出兩個範圍: 和
因此,我們希望尋找 在這兩個範圍內的個數
Subsubproblem
對於結果 的第 位,現有 是對 對於 取模,求 兩兩之和在 和 的個數。
(簡化版)給定數組 ,求其兩兩和在某一區間的個數。
這裏爲簡化,假設求的區間是
暫時只有 的解法,未知是否有更迅速的
首先將 從小到大排序,
然後對 ,它可以相加的數爲 , …, 。
用二分查找尋找下界值爲 的下標 ,
用二分查找尋找上界值爲 的下標 。
滿足的個數就是 。
代碼
#include <bits/stdc++.h>
using namespace std;
long long a[400005], b[400005];
int main()
{
ios::sync_with_stdio(false);
long long n, i, j, k, t, m, p, q, s;
cin >> n;
m = 0;
for (i = 0; i < n; i++) {
cin >> a[i];
m = max(a[i], m);
}
m = log2(m) + 2;
s = 0;
for (i = 0; i <= m; i++) {
k = pow(2, i+1);
for (j = 0; j < n; j++) {
b[j] = a[j] % k;
}
sort(b, b+n);
t = 0;
for (j = 0; j < n; j++) {
// 2^i, 2^(i+1)
p = lower_bound(b+j+1, b+n, k/2 - b[j]) - b;
q = lower_bound(b+j+1, b+n, k - b[j]) - b;
t += q - p;
// 2^i + 2^(i+1), 2^(i+2)-1
p = lower_bound(b+j+1, b+n, k/2*3 - b[j]) - b;
q = lower_bound(b+j+1, b+n, 2*k - 1) - b;
t += q - p;
}
if (t % 2 == 1) {
s += k / 2;
}
}
cout << s << endl;
return 0;
}