hdu暑期多校第一场 Operation (线性基)

本文深入探讨了线性基算法,一种处理异或运算的强大工具。通过构造线性基,可以高效解决数列中元素异或的最大值、最小值及第k小值问题。文章详细解释了线性基的构建过程、性质及其应用,并提供了具体实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线性基:处理异或的有力工具。线性基是一个集合,由已知的集合求出。线性基有如下性质。

先看线性基是如何构造的:

其中 d[] 数组保存线性基各元素,d[i] 的第 i+1位是1,且第i+1位是最高位。d[i] 是由若干个已知集合中的元素异或得到的。

LL d[60]; //保存线性基各元素
void Add(LL x){
    for(int i=60; i>=0; i--){
        if(x >> i) { //第i+1位是1
            if(d[i]) x ^= d[i];
            else { d[i] = x; break; }
        }
    }
}
  1. 集合中的任意元素可以由线性基中的某些元素异或得到。
  2. 线性基中的任意若干元素异或起来都得不到0。
  3. 线性基的元素个数一定,且在保证性质1的条件下,元素个数是最少的。

各性质相关证明 https://blog.youkuaiyun.com/a_forever_dream/article/details/83654397 

有了线性基之后可以求:在一个数列中取若干数进行异或能得到的最大值、最小值、第 k 小值

最大值:贪心即可,从高位到低位

LL Xormax(){
    LL ans = 0;
    for(int i=60; i>=0; i--)
        if((ans^d[i]) > ans)
            ans ^= d[i];
    return ans;
}

最小值:是线性基中最小的数

LL Xormin(){
    for(int i=0; i<=60; i++)
        if(d[i]!=0) return d[i];
}

第k小值:要将 d[] 中的数组处理一下。将每个d[i]二进制中除最高位之外的,且能被d[j]最高位的1异或掉的1消去。换句话说就是保证只有d[i] 的二进制的第 i+1位是1,其他d[j] 的第 i+1 位一定是0。
例如:1001101                    处理完之后: 1000100
                 1011                                                 1001
                     10                                                     10

LL k_thmin(int k){
    if(k == 1 && tot < n) return 0;//假如k=1(求最小),并且原来的序列可以异或出0,就要返回0,tot表示线性基中的元素个数,n表示序列长度
    if(tot < n) k--;      //类似上面,去掉0的情况,因为线性基中只能异或出不为0的解
    for(int i=1; i<=60; i++)
        for(int j=1; j<=i; j++)
            if(d[i] & (1LL<<(j-1)))
                d[i] ^= d[j-1];
    LL ans = 0;
    for(int i=0; i<=60; i++)
        if(d[i] != 0) {
            if(k&1) ans ^= d[i];
            k >>= 1;
        }
    return ans;
}

题目:

http://acm.hdu.edu.cn/contests/contest_showproblem.php?pid=1002&cid=848

一个数列,若干操作,两种类型操作:0、询问 [l, r] 内取若干元素异或和最大值。1、在数列末尾添加一个数x。其中的 l ,r , x 的值还有些其他处理。

思路:先只考虑第一个条件,询问区间内的异或和最大值,可以用一个pos数组记录每次Add操作时的位置,求最大值时加上一个判断条件即可。考虑第二个条件,每次加上一个元素时相当于再进行一次Add操作,每次记录添加一个值时的线性基,把数组开成二维即可。

与正常线性积区别就是:正常情况下线性积中保存的是先插进来的数,而现在要尽量将后插入的数存在线性积中,所以有插入过程中多了一步判断交换步骤。

代码如下:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6+5;

struct Line_Base{
    const static int sz = 35;
    int n, a[maxn][sz], b[maxn][sz];
    void build(){n=0;}
    void add(int x){
        int cur = ++n;
        for(int i=31; i>=0; --i){
            a[n][i] = a[n-1][i];
            b[n][i] = b[n-1][i];
        }
        for(int i=31; i>=0; --i){
            if(x >> i){
                if(!a[n][i]){
                    a[n][i] = x;
                    b[n][i] = cur;
                    break;
                }else {
                    if(cur > b[n][i]) swap(a[n][i], x), swap(b[n][i], cur);
                    x ^= a[n][i];
                }
            }
        }
    }
    int query(int l, int r){
        l = l%n + 1; r = r%n + 1;
        if(l > r) swap(l, r);
        int ret = 0;
        for(int i=31; i>=0; --i)
            if(b[r][i] >= l)
                ret = max(ret, ret^a[r][i]);
        return ret;
    }
}b;
int main()
{
    int t, n, m, x, y, op;
    scanf("%d", &t);
    while(t--){
        b.build();
        scanf("%d%d", &n, &m);
        for(int i=0; i<n; i++) scanf("%d", &x), b.add(x);
        int ans = 0;
        for(int i=0; i<m; i++){
            scanf("%d", &op);
            if(op == 0){
                scanf("%d%d", &x, &y);
                printf("%d\n", ans=b.query(x^ans, y^ans));
            }else scanf("%d", &x), b.add(x^ans);
        }
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值