1. 程式人生 > >數論——異或,兩道水題。

數論——異或,兩道水題。

lld col tps include ons 區間 二進制 統計 但是

第一題:(沒有鏈接)

題目大意:給你n個數(n <= 1000000),第i個數用ai表示(每個a <= 1000000),求出每個數與其之後的數的xor和。

舉個例子吧,比如三個數1 2 3答案就應該為1 xor 2 + 1 xor 3 + 2 xor 3 = 4;

題解:

首先有一個O(n*n)的算法,就是暴力枚舉,可以過40%數據。

程序大體:

for (int i = 1;i < n;i ++)
    for (int j = i + 1;j <= n;j ++) ans += a[i] ^ a[j];

很明顯TLE,考慮一下兩個數的二進制的某一位xor能產生貢獻的條件是什麽?就是兩位不同。。。 也就是其中一個是1,另一個是0二進制下的這一位就可以產生貢獻。咦?好像有很大的用。那麽我們可以很容易的想到

乘法原理。。。怎麽用呢,用cnt[i]表示n個數在二進制下的第i為1的有多少個,那麽為0的自然就是n - cnt個,我們可以這樣考慮,讓每一個這一位為1的和每一個這一位為0的進行xor,這樣是可以產生貢獻的。然後就用cnt * (n - cnt)種搭配能讓這一位產生貢獻(很明顯這些搭配沒有重復),那麽只要讓cnt * (n - cnt)乘以1 << k -1(k表示當前是第幾個二進制位)就可以得到第k位的和了。

代碼如下:

#include <cstdio>
#include <iostream>

using namespace std;

int n; long long ans, a; long long cnt[30]; int main(){ scanf("%d", &n); for (int i = 1;i <= n;i ++){ scanf("%lld", &a); for (int i = 1;i <= 20;i ++) if (1 << (i-1) & a) cnt[i]++; } for (int i = 1;i <= 20;i ++) ans += cnt[i] * (n-cnt[i]) * (1
<< i-1); printf("%lld", ans); } //tips:別忘了用long long

第二題:題目鏈接

題目大意:給你n個數(n <= 1e5),每個數為ai(每一個a <= 1e9)求每個區間xor值的和,這裏吐槽一下,說的數據是1e9實際只有2的16次方的數據(洛谷第一個討論看見的)

舉個例子吧,比如兩個數1 2,你需要輸出的就是1 + 2 + 1 xor 2 = 6,單獨的一個數也叫一個區間。

題解:同樣的暴力枚舉每一區間復雜度O(n*n),TLE。。。做這道題需要知道若a xor b = c, 那麽a xor c = b;(我不會證明,這是xor的運算性質),然後我們同樣的向二進制方向想,對於每一位產生貢獻進行考慮,若[l, r]這個區間對於這一位能產生貢獻的條件就是其中1的個數為奇數且0的個數為偶數,或者0的個數為奇數且1的個數為偶數。那麽我們可以求出xor的前綴也就是sum[i]表示前i個數的異或值。那麽統計答案只需要求出對於二進制下的每一位這n個前綴中有幾個1,用cnt表示,有幾個0(也就是n - cnt)然後讓每一個1與每一個0組合形成一個區間然後統計就是cnt (n-cnt) 1 << (k -1);

但是這樣是錯的,你會發現你讓他們組合了卻沒有考慮到1-X這樣的區間也就是讓還有cnt個也可以產生貢獻。所以答案統計應該是(cnt (n - cnt) + cnt) 1 << (k - 1)也就是cnt (n - cnt + 1) 1 <<(k -1);

代碼:

#include <cstdio>
#include <iostream>

const int maxn = 100010;

using namespace std;

int n;
long long ans;
long long sum[maxn];

int main(){
    scanf("%d", &n);
    for (int i = 1;i <= n;i ++) scanf("%lld", &sum[i]), sum[i] = sum[i-1] ^ sum[i];
    for (int i = 1;i <= 17;i ++){
        long long cnt = 0;
        for (int j = 1;j <= n;j ++) if (sum[j] & (1 << (i-1))) cnt ++;
        ans += cnt * (n - cnt + 1) * (1 << i - 1);
    }
    printf("%lld", ans);
    return 0;
}
//tips:別忘了開long long

蒟蒻開始跑路。。。

數論——異或,兩道水題。