Codeforces——1322B.Present

本文首发于我的blog,欢迎点击查看(无广告界面清爽!)

题目网址: https://codeforces.com/contest/1322/problem/B

写这篇博文是因为第一次遇到这个解法,对于我这个算法小白来说还是很新颖的。
PS.做题的时候天真的以为是O(n)的解法,并且可以用数学做。。。

题目

给定 n n n个数 a 1 a_1 a1, a 2 a_2 a2, … , a n a_n an
计算其两两之和的异或值。

分析

首先输入大小以看就不可以直接求(废话)
复杂度也就是压在O(nlogn)左右

关键点
我们可以把答案回到二进制,然后对二进制的每一位分开求。

原理

首先我们定义 x i = a k + a j x_i = a_k + a_j xi=ak+aj, 即 x i x_i xi 是两数之和。
s s s 是答案, s i s_i si 是指从右往左答案的第 i i i 个二进制位(从0开始)

我们现在假设计算 s i s_i si,我们已知所有 x x x 的第 i i i 位的01情况,那么求 s i s_i si 就是对它们做异或,也就等于计算 x x x 中第 i i i 位是1的个数的奇偶情况。
即如果1有偶数个,那么答案对应的位数是0,反之则是1。(简单的异或运算原理)

Subproblem

对于结果 s s s,计算第 i i i 位的值,即 s i s_i si

(等价问题)计算两数之和 x x x的第 i i i 位是 1 1 1 的数量。

对于这个等价问题,我们先思考一个简单的情况:

假设现在有一个数 y < 2 i + 1 y < 2^{i+1} y<2i+1,它的第 i i i位的是否位 1 1 1取决于它是否在 [ 2 i , 2 i + 1 ) [2^{i}, 2^{i+1}) [2i,2i+1) 内。
现在把 y y y 扩大为正数,我们可以得到其第 i i i位的情况与 $y’ = y \% 2^{i+1} $。

因此,对于每个 x x x,我们可以只考虑 $x \% 2^{i+1} $。

但我们希望能通过 a a a 直接计算出个数,而非计算 x x x 后求出解。
既然我们可以对 x x x 取模,自然也可以对 a a a 取模,这对加法并不会有影响。

因此,我们首先将 a a a 2 i + 1 2^{i+1} 2i+1 取模,令取模后的数为 b b b
我们可以得到 b ∈ [ 0 , 2 i + 1 ) b \in [0, 2^{i+1} ) b[0,2i+1)
即有 x ∈ [ 0 , 2 i + 2 − 1 ) x \in [0, 2^{i+2}-1 ) x[0,2i+21)

我们发现 x x x 的范围缩小了很多,这次我们不对 x x x 取模,
相反,我们可以直接对 x x x 划出两个范围: [ 2 i , 2 i + 1 ) [2^i, 2^{i+1}) [2i,2i+1) [ 2 i + 2 i + 1 , 2 i + 2 − 1 ) [2^i + 2^{i+1}, 2^{i+2}-1 ) [2i+2i+1,2i+21)

因此,我们希望寻找 x x x 在这两个范围内的个数

Subsubproblem

对于结果 s s s 的第 i i i 位,现有 b b b 是对 a a a 对于 2 i + 1 2^{i+1} 2i+1 取模,求 b b b 两两之和在 [ 2 i , 2 i + 1 ) [2^i, 2^{i+1}) [2i,2i+1) [ 2 i + 2 i + 1 , 2 i + 2 − 1 ) [2^i + 2^{i+1}, 2^{i+2}-1 ) [2i+2i+1,2i+21) 的个数。

(简化版)给定数组 b b b,求其两两和在某一区间的个数。

这里为简化,假设求的区间是 [ k , t ) [k, t) [k,t)

暂时只有 O ( n l o g n ) O(nlogn) O(nlogn) 的解法,未知是否有更迅速的

首先将 b b b 从小到大排序,
然后对 b i b_i bi ,它可以相加的数为 b i + 1 b_{i+1} bi+1, …, b n b_n bn
用二分查找寻找下界值为 k − b i k-b_i kbi 的下标 p p p
用二分查找寻找上界值为 t − b i t-b_i tbi 的下标 q q q
满足的个数就是 q − p q-p qp

代码

#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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值