codeforces 1323D 题解(数学)

题意:给\(n\)个数\(a_1,a_2,...,a_n\),计算\(\mathop{\oplus}\limits_{i<j}(a_i+a_j)\)\(\oplus\)是异或。

\(n\leq 400000,1\leq a_i\leq 10^7\)

按二进制位考虑,从低位往高位看,最低位记作第\(0\)位,以此往上。考虑第\(p\)位是\(0\)还是\(1\):第\(p\)位是\(1\),当且仅当有奇数对\(i,j\)使得\(a_i+a_j\)的第\(p\)位是\(1\)。由于加法运算的高位不会影响低位,那就干脆忽视高位,只看第\(p\)位及以下的位。忽视高位后(也就是把数都\(\%2^p\)后),显然有\(0\leq a_i,a_j<2^p\),那么\(0\leq a_i+a_j<2^{p+1}\)。因此\(a_i+a_j\)的第\(p\)位是\(1\),当且仅当\(2^p\leq a_i+a_j<2^{p+1}\)\(2^{p+1}+2^p\leq a_i+a_j\)

那么可以枚举\(i\),查询有多少个\(j\)使得\(2^p\leq a_i+a_j<2^{p+1}\)\(2^{p+1}+2^p\leq a_i+a_j\)。换句话说,查询有多少个\(a_j\in[2^p-a_i,2^{p+1}-a_i)\cup[2^{p+1}+2^p-a_i,+\infin)\)

那么把数组排序,二分找到这些端点的位置就可以轻松统计出个数了。当然,还有一些细节,比如\(i\neq j\),还有每对数被算了两遍什么的,要慢慢去处理。

\(A=\mathop{max}\limits_{i=1}^n(a_i)\),时间复杂度\(O(n\ logn\ logA)\)。如果用双指针(当然这里有好多指针),能降低到\(O(n\ logA)\)。(我没有实际写这个双指针代码,如果无法做到请直接评论区开喷(

PS:我在赛中写的是\(O((n+2A)\ log2A)\)的,因为我没想到排序二分,就直接\(O(2A)\)打前缀和表了,但还是1400ms过了。。。感谢出题人爸爸不杀之恩

//O(n+2A log2A)的
#include <bits/stdc++.h>
#define LL long long
#define pb push_back
#define eb emplace_back
#define pii pair<int,int>
#define pll pair<LL,LL>
using namespace std;

int n, a[400008], cnt[20000008], v[400008];
int ans[32];
int fans = 0;
int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
    {
        scanf("%d", &a[i]);
    }
    for (int i = 0; i < 25; ++i)
    {
        //cout << "i = " << i << endl;
        for (int j = 1; j <= n; ++j)
        {
            v[j] += (a[j] & (1 << i));
        }
        memset(cnt, 0, sizeof(cnt));
        for (int j = 1; j <= n; ++j)
        {
            cnt[v[j]]++;
        }
        for (int val = 1; val <= 20000000; ++val)
        {
            cnt[val] += cnt[val - 1];
        }
        for (int j = 1; j <= n; ++j)
        {
            //cout << "j = " << j << endl;
            int l = ((1 << i) - v[j]), r = ((1 << (i + 1)) - 1 - v[j]);
            int l2 = (1 << (i + 1)) + (1 << i) - v[j];
            //cout << l << "  " << r << endl;
            if (l - 1 >= 0)
                ans[i] += cnt[min(r, 20000000)] - cnt[l - 1] - (v[j] >= l && v[j] <= r);
            else
                ans[i] += cnt[min(r, 20000000)] - (v[j] >= l && v[j] <= r);
            /*if (l - 1 >= 0)
                cout << cnt[min(r, 20000000)] - cnt[l - 1] - (v[j] >= l && v[j] <= r) << endl;
            else
                cout << cnt[min(r, 20000000)] - (v[j] >= l && v[j] <= r) << endl;*/
            if (l2 >= r + 1)
                ans[i] += n - cnt[min(r + (1 << i), 20000000)] - (v[j] >= l2);
        }
        ans[i] /= 2;
        //cout << ans[i] << endl;
        if (ans[i] % 2)
            fans += (1 << i);
    }
    cout << fans << endl;
}

后记

感觉自己很无敌,但其实看来这题放在真的现场赛中也就是个银牌题中比较简单的题目,不知道为什么cf div2比赛中只有几十个人过,大概年轻人还是要多学习一个

猜你喜欢

转载自www.cnblogs.com/zhugezy/p/12484180.html