题解 [ABC249G] Xor Cards

题目链接

AtCoder

题目描述

\(n\) 张卡片,第 \(i\) 张卡片正面的数字是 \(a_i\),背面的数字是 \(b_i\)

给定整数 \(k\),要求选若干张(至少一张)卡片,使得这些卡片中正面数字异或和小于等于 \(k\),在此要求下最大化背面数字异或和,并输出最大异或和。

如果没有方案使得卡片的正面数字异或和小于等于 \(k\),输出一行一个整数 \(-1\)

其中 \(a_i, b_i \le 2^{30}\)

分析

题目要求若干个数的异或最大值,对于这个问题我们除了线性基似乎很难想到别的方法。

我们发现对于一个方案,\(a\) 的值只有异或和有用,\(b\) 的值也只有异或和有用,所以我们可以直接将 \(n\) 张卡片插入进线性基里。

上面的话讲的可能有些乱,我们不妨换一种方式描述这个题目(这也是下面的参考代码解决的问题):

\(n\) 张卡片,第 \(i\) 张卡片有一个权值 \(a_i \times 2^{30} + b_i\),从其中选若干张卡片,使得前 \(30\) 位小于等于 \(k\),并最大化后 \(30\) 位的值。

而在这 \(n\) 张卡片中选若干张,能选出的权值集合可以用插入了这 \(n\) 张卡片的线性基表示

而这个问题可以通过逐位考虑解决

具体而言,我们将 \(n\) 张卡片插入线性基时,对于 \(2^i(i > 30)\) 位,如果线性基在这一位上有值,那么对于线性基中的其他值,我们都要保证第 \(i\) 位为 \(0\)

如果你不知道这样的线性基怎么实现,可以参考 Menci 的博客

当第 \(i\) 张卡片插入线性基时,如果他进入了第 \(31\) 位(此时他的前 \(30\) 位均为 \(0\))那我们直接把他剩下的值插入进另一个线性基内。

因为既然他前 \(30\) 为都是 \(0\) 了,那他已经可以完全摆脱 \(k\) 的限制,选不选都无所谓了。

现在考虑 \(k\) 的限制,我们从小到大考虑每一位,假设 \(k\) 的第 \(i\) 位为 \(1\),那如果我们在线性基中选数使得其前 \(i - 1\) 为与 \(k\) 相同并且第 \(i\) 位为 \(0\),那第 \(i + 1\)\(30\) 位就可以随便选了,这个容易用我们刚才讲的 “另一个线性基” 解决。

还有一种情况就是 \(n\) 张卡片的前 \(30\) 位无法异或得到 \(k\),那我们找到第一个会超过 \(k\) 的位(即前 \(i - 1\) 位可以与 \(k\) 相等,但是一异或第 \(i\) 位上的值就会大于 \(k\)),这一位之后的位都可以随便选,从这一位开始向前考虑即可。

复杂度 \(O(n \log V)\),其中 \(V\) 为值域中的最大值。

强烈推荐 AtCoder 官方题解,在矩阵上用异或进行高斯消元的方法解决异或问题是我从未想过的(虽然本质上和线性基没什么区别)。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
 
const int N = 1010;
const LL K1 = (1 << 30) - 1;
const LL K2 = (1ll << 60) - (1ll << 30);
 
LL n, k, d, p[N], p1[N], used[N];
 
LL que(LL res) // 初始值为 res 的异或最大值
{
    for(int i = 30; i; i--)
        if((res ^ p1[i]) > res) res ^= p1[i];
    return res;
}
 
void ins(LL x)
{
    for(int i = 30; i; i--){
        if(!(x & (1ll << (i - 1)))) continue;
        if(!p1[i]) return p1[i] = x, void();
        x ^= p1[i];
    }
}
 
void add(LL x)
{
    for(int i = 60; i >= 30; i--){
        if(i == 30) d = 1, ins(x & K1); // 如果进入后三十位,直接将其插入另一个线性基
        if(!(x >> (i - 1))) continue;
        if(!p[i]){
            for(int j = i - 1; j > 30; j--)
                if(x & (1ll << (j - 1))) x ^= p[j]; // 向后调整,把后面的 1 去掉
            for(int j = i + 1; j <= 60; j++)
                if(p[j] & (1ll << (i - 1))) p[j] ^= x; // 向前调整
            p[i] = x;
            break;
        }
        x ^= p[i];
    }
}
 
int main()
{
    ios :: sync_with_stdio(false), cin.tie(0);
    cin >> n >> k, k <<= 30;
    for(LL i = 1, a, b; i <= n; i++)
        cin >> a >> b, add((a << 30) + b);
 
    LL cur = 0, now = 0, ans = 0;
    for(int i = 60; i > 30; i--)
        if(((cur ^ p[i]) & K2) <= k && p[i])
            cur = (cur ^ p[i]) & K2, now = now ^ (p[i] & K1),
            used[i] = 1, d = i;
 
    ans = que(now);
    for(int i = 31; i <= 60; i++){
        if(used[i])
            now ^= (p[i] & K1), ans = max(ans, que(now)), ins(p[i] & K1);
        else{
            if(k & (1ll << (i - 1))) ans = max(ans, que(now));
            ins(p[i] & K1);
        }
    }
    if(!d) return puts("-1"), 0;
    cout << ans << endl;
    return 0;
}
posted @ 2022-05-10 22:32  sgweo8ys  阅读(183)  评论(0)    收藏  举报