2024ccpc线性基与校赛线性基

异或空间线性基

我终于意识到写题解有多重要了

2024 CCPC 网络赛 Problem J. 找最小

Mandy 发现了两个很好玩的长度为n的序列,记为a,b,她觉得一个序列的无趣度为序列内所有元素的异或和。

现在她想要这两个序列尽可能有趣,具体来说,她希望最无趣的序列尽可能有趣。她觉得交换两个序列中对应位置的元素是无伤大雅的,可以进行任意次这样的操作。

现在她想要知道,最有趣的情况下两个序列无趣度较大者的无趣度是多少呢?

形式化地来说,你有两个长度为 n 的序列 a,b ,你可以进行若干次选择一个 i(1in) ,然后交换 ai,bi ,希望使得 max{f(a),f(b)} 最小,其中对于一个序列 A,f(A)=i=1nAi (表示按位异或) 。

Output

第一行一个整数 T(1T105), 表示数据组数。

对于每组数据,第一行一个整数 n (1n106)

第二行 n 个整数 a1,a2,,an (0ai<231)

第三行n个整数b1,b2,,bn (0bi<231)

保证对于所有数据,n106

Solution

首先有一个重要观察,如果当前两个数组的异或和分别是 AB, 使得 aibi 交换的操作即为

AaibiBaibi

ci=aibi , 在数组 c 中选择若干个数字异或

于是可以考虑线性基,设异或出的数字为 X

问题进展到这里,我们对 X 希望是什么样子的已经可以贪心得到了,从高位到低位贪心。

而线性基刚好异或上这一个,那么这个对应的这一位就在结果中出现,不异或这一个,就不会出现。

接下来就是分类讨论了,当前位若 Ai=Bi ,则去看线性基有没有这一位再直接选择异或或者不异或,我们可以强制 A>B,不然交换位置。那么第一个出现不同的情况的时候,肯定是 Ai=1Bi=0 的情况,这时候我们如果选择异或这一位的话,那么 B 求出的答案就肯定大于 A 求出的答案了,所以,之后所有的选择都得偏袒 B。 相当于(在大的情况下我偏袒了 A,那么在每一个小的情况,我都需要去补偿 B,相反也是一样的)

下面是参考代码

点击查看代码
// Created by qyy on 2024/9/9.

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

#define PII pair<int, int>
#define endl "\n"

const long long inf = 0x3f3f3f3f3f3f3f3f;
const int N = 1e6 + 10;
const int mod = 1e9 + 7;

int n;
ll a[N], b[N];

bool zero;
ll p[N];
int xxjcnt;
int Gauss(){
    // 此构造线形基出的是降序排序,大小为 xxjcnt 个,编号从 1 开始
    for(int i = 1; i <= n; i++){
        p[i] = a[i];
    }
    int i, k = 1;
    ll j = (ll) 1 << 62;  // 注意不是 63;
    for(; j; j >>= 1){
        for(i = k; i <= n; i++) {
            if (p[i] & j) {
                break; // 找到了第 j 位上的 1
            }
        }
        if(i > n){
            continue; // 没有找到第 j 位上的 1
        }
        swap(p[i], p[k]);
        for(i = 1; i <= n; i++){
            if(i != k && p[i] & j){
                p[i] ^= p[k];
            }
        }
        k++;
    }
    k--;
    if(k != n){
        zero = true;
    }else{
        zero = false;
    }
    return k;
}

ll toj[100]; // 需要更新;
int cal(ll x){
    for(int i = 62; i >= 0; i--){
        if((1LL << i) & x){
            return i;
        }
    }
}

void solve() {
    cin >> n;
    for(int i = 0; i < 90; i++){
        toj[i] = 0;
    }
    ll A = 0, B = 0;
    for(int i = 1; i <= n; i++){
        cin >> a[i];
        A ^= a[i];
    }
    for(int i = 1; i <= n; i++){
        cin >> b[i];
        B ^= b[i];
    }
    for(int i = 1; i <= n; i++){
        a[i] ^= b[i];
    }
    if(A < B){
        swap(A, B);
    }

    xxjcnt = Gauss();
    for(int i = 1; i <= xxjcnt; i++){
        int x = cal(p[i]);
        toj[x] = p[i];
    }
    ll ans1 = 0, ans2 = 0;
    bool flag = false;
    for(int i = 62; i >= 0; i--){
        ll x = (1LL << i);
        if((x&A) && (!(x&B))){
            // 开始分类;
            if(toj[i] != 0){
                if(flag){
                    ans2 ^= toj[i]; // 应该按照 B 小的走,因为现在 B 异或出来一定比 A 大
                }else{
                    ans1 ^= toj[i]; // 应该按照 B 小的走,因为现在 B 异或出来一定比 A 大
                }
            }
            flag = true;
        }else if((x&A) && (x&B)){
            // 需要 toj[i] 参与进来
            if(toj[i] != 0){
                ans1 ^= toj[i];
                ans2 ^= toj[i];
            }
        }else if((!(A&x)) && (!(B&x))){
            // 不需要任何参与
        }else{
            // 给两个答案都计算上
            if(toj[i] != 0){
                ans1 ^= toj[i];
            }
        }
    }
    ll res1 = max(A^ans1, B^ans1);
    ll res2 = max(A^ans2, B^ans2);
    cout << min(res1, res2) << endl;
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    int t = 1;
    cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}


2024 校赛(团队赛)

作为 xxcdsg 的舔狗,Taibo 经常邀请 xxcdsg 一起玩无聊的游戏。

有一个长度为 n 的序列 [a1,a2,...,an] , Taibo 和 xxcdsg 轮流在这个序列中取一个非空子序列并删除该子序列直到序列为空,Taibo 是先手。 游戏结束后,定义游戏的得分为 Taibo 和 xxcdsg 每次所取的子序列的异或和的总和。

现在,Taibo 想要最大化这个得分,而 xxcdsg 则想要最小化该得分。如果双方都采取最佳策略的情况下,最终的得分将是多少?关于非空子序列的定义:如果正整数 i1,i2,...,ik(k1) 满足1i1<i2<...<ikn, 那么 [ai1,ai2,...,aik][a1,a2,...,an] 的一个非空子序列。

例如,[1,2,6],[1,3,5],[4],[1,3,5,2,4,6]是[1,3,5,2,4,6]的非空子序列,而[1,2,3],[4,5],[1,2,3,4,5,6]不是。

n2×105,0ai<220

solution

首先一个重要观察,不管 Taibo 第一轮选了什么,剩下的所有数 xxcdsg 一定会全部全部拿光。原因即异或是不进位加法,aba+b ,于是问题简化为在一个数组中选择若干个数异或,加上剩余的数异或,求最大结果。

观察到数据范围,线性基后,可以搜索出每一种可能,于是枚举取最大值就行了。

参考代码如下:

点击查看代码
// Created by qyy on 2024/9/9.

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

#define PII pair<int, int>
#define endl "\n"

const long long inf = 0x3f3f3f3f3f3f3f3f;
const int N = 2e5 + 10;
const int mod = 7;

int n;
ll a[N];

bool zero;
ll p[N];
int xxjcnt;
int Gauss(){
    // 此构造线形基出的是降序排序,大小为 xxjcnt 个,编号从 1 开始
    for(int i = 1; i <= n; i++){
        p[i] = a[i];
    }
    int i, k = 1;
    ll j = (ll) 1 << 62;  // 注意不是 63;
    for(; j; j >>= 1){
        for(i = k; i <= n; i++) {
            if (p[i] & j) {
                break; // 找到了第 j 位上的 1
            }
        }
        if(i > n){
            continue; // 没有找到第 j 位上的 1
        }
        swap(p[i], p[k]);
        for(i = 1; i <= n; i++){
            if(i != k && p[i] & j){
                p[i] ^= p[k];
            }
        }
        k++;
    }
    k--;
    if(k != n){
        zero = true;
    }else{
        zero = false;
    }
    return k;
}

ll ans = 0, sum = 0;
void dfs(int pos, ll cur){
    if(pos == xxjcnt + 1){
        ans = max(ans, cur + (sum^cur));
        return ;
    }
    dfs(pos + 1, cur);
    cur ^= p[pos];
    dfs(pos + 1, cur);
}
void solve() {
    cin >> n;
    for(int i = 1; i <= n; i++){
        cin >> a[i];
        sum ^= a[i];
    }
    xxjcnt = Gauss();
    ans = sum;
    dfs(1, 0);
    cout << ans << endl;
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    int t = 1;
    //cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}

posted @   9102700  阅读(169)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
  1. 1 鼓楼 赵雷
  2. 2 我们的歌 王力宏
  3. 3 老街 李荣浩
  4. 4 周杰伦
  5. 5 可惜没如果 林俊杰
  6. 6 不将就 李荣浩
  7. 7 南方姑娘 赵雷
  8. 8 南方姑娘(弹唱版) 赵雷
  9. 9 如果可以 韦礼安
  10. 10 写给黄淮 邵帅
  11. 11 我想念 汪苏泷
  12. 12 雨天 汪苏泷
  13. 13 雨天雨天 汪苏泷
  14. 14 成都 赵雷
我们的歌 - 王力宏
00:00 / 00:00
An audio error has occurred, player will skip forward in 2 seconds.

作词 : 王力宏/陈信延

作曲 : 王力宏

编曲 : 王力宏

已经听了一百遍

已经听了一百遍

怎么听都不会倦

从白天唱到黑夜

你一直在身边

如果世界太危险

只有音乐最安全

带着我进梦里面

让歌词都实现

无论是开心还是难过我的爱一直不变

不必担心时间流逝带走一切

无论是Hip-Hop 还是摇滚我的爱一直不变

所有美好回忆记录在里面

这种 Forever Love 那么深

我们的歌 那么真

无国界跨时代

再也不会叫我Kiss Goodbye

要每一句能够动人心弦 Yeah

情人总分分合合

可是我们却越爱越深

认识你 让我的幸福如此悦耳

能不能不要切歌

继续唱我们的歌​​

让感动一辈子都记得

已经听了一百遍

已经听了一百遍

怎么听都不会倦

从白天唱到黑夜

你一直在身边

如果世界太危险

只有音乐最安全

带着我进梦里面

让歌词都实现

无论是开心还是难过我的爱一直不变

不必担心时间流逝带走一切

无论是Hip-Hop 还是摇滚我的爱一直不变

所有美好回忆记录在里面

这种 Forever Love 那么深

我们的歌 那么真

无国界跨时代

再也不会叫我Kiss Goodbye

要每一句能够动人心弦 Yeah

情人总分分合合

可是我们却越爱越深

认识你 让我的幸福如此悦耳

能不能不要切歌

继续唱我们的歌​​

让感动一辈子都记得

情人总分分合合

情人总分分合合

可是我们却越爱越深

认识你 让我的幸福如此悦耳

能不能不要切歌

继续唱我们的歌​​

让感动一辈子都记得

电吉他/其他乐器:王力宏

鼓手:Eric Fawcrtt

贝斯:John Mumson

录音师/录音室:王力宏/Homeboy Music Studios,Taipei

OP:HIM Music Publishing Inc.

OP:Homeboy Music,Inc,Taiwan

SP:Sony Music Publishing(Pre)Ltd.Taiwan Branch

点击右上角即可分享
微信分享提示