线性基 (异或和求最大值)例题:无效位置

对于给出多个数,求异或的最大值时,通常需要用线性基来处理

什么是线性基呢?

对于原数组,我们试图用最少个数作为基础,用这些数之间的异或 就可以 表示所以子集合的异或和,而这些数即基础,称之为线性基,类似于用10个数来表示1~1023的所有数的道理

所有基线性相关


插入

我们用数组a来表示线性基,a[i]表示基当中最高位为第i位的基

基的插入过程为

  1. 假如当前数v最高位i所在的基a[i]已经存在(a[i]!=0),那么这个数通过基a[i]转化成低位的数:v^=a[i]
  2. 假设不存在,就拿当前数作为基a[i]

eg:
假设给出下列二进制数
1100 ,1011 ,1110 ,1101 ,0001 ,0101

  1. 1100,因为a[4]==0 所以a[4]=1100
  2. 1011,a[4]存在,所以1011->0111,故a[3]=0111
  3. 1110,a[4]存在,->0010,a[2]=0010
  4. 1101,a[4]存在,->0001,a[1]=0001
  5. 0001,a[1]存在,->0000
  6. 0101,a[3]存在,->0010,a[2]存在,->0000

很容易发现,只要满基了,就可以表示所有的数了,因为相当于二进制每一位都可以控制0或1

本质:低位基都是由数经过高位基转化而来

void insert(int val){
    for(int i=31;i>=1;i--){
        int j=i-1;
        if(val&(1<<j)){
            if(!a[i]){
                a[i]=val;break;
            }
            val^=a[i];
        }
    }
    if(val==0)a[0]=1;//可以通过异或变成0,用于找最小值
}

验证存在性

和插入的过程类似,从高到低,只要同位的最高位基存在,就异或,到最后成功由这些基转化成0就说明存在

int fin(int val){
    if(val==0&&a[0])return 1;
    for(int i=31;i>=1;i--){
        int j=i-1;
        if(val&(1<<j)){
            val^=a[i];
            if(!val)return 1;
        }
    }
    return 0;
}

合并

因为基的性质,只要把另一个线性基里面的基当成数插入就可以了(相当于把其他的基通过当前线性基内的基转化一下)

void merge(int b[]){
    for(int i=31;i>=1;i--){
        if(!b[i])continue;
        insert(b[i]);
    }
}

查找最值

所有原数之间的异或和等价于基之间的异或和,所以找最大值,只需要判断所有基的取与不取即可

找最大值时,从高位基开始找,取某个基时使答案变大,那么就是需要取的

int finmax(){
    int ans=0;
    for(int i=31;i>=1;i--){
        if(ans^a[i]>ans)ans^=a[i];
    }
    return ans;
}

找最小值时,因为基代表了最高位是哪一位,所以最小的那个基就是答案(当然,要考虑一个基都没有的情况)

int finmin(){
    if(a[0])return 0;
    for(int i=1;i<=31;i++){
        if(a[i])return a[i];
    }
}

模板


#include<bits/stdc++.h>
using namespace std;
struct linear_Bace;
typedef linear_Bace LB;
#define N 100009

struct linear_Bace{
    int a[33];
    void insert(int val){
        for(int i=31;i>=1;i--){
            int j=i-1;
            if(val&(1<<j)){
                if(!a[i]){
                    a[i]=val;break;
                }
                val^=a[i];
            }
        }
        if(val==0)a[0]=1;//可以通过异或变成0
    }
    int fin(int val){
        if(val==0&&a[0])return 1;
        for(int i=31;i>=1;i--){
            int j=i-1;
            if(val&(1<<j)){
                val^=a[i];
                if(!val)return 1;
            }
        }
        return 0;
    }
    int finmax(){
        int ans=0;
        for(int i=31;i>=1;i--){
            if((ans^a[i])>ans)ans^=a[i];
        }
        return ans;
    }
    int finmin(){
        if(a[0])return 0;
        for(int i=1;i<=31;i++){
            if(a[i])return a[i];
        }
        return 0;
    }
}e[N];

void merge(LB &a,LB &b){//a和b都变成a+b
    for(int i=31;i>=1;i--){
        if(b.a[i]==0)continue;
        a.insert(b.a[i]);
    }
    b=a;
}

例题

原题Wannafly挑战赛14 E-无效位置

题意

给出n个数,会依次删除所有数,求每次删除前剩下数组ans

定义ans=从任意一个完整区间(连续的没有数被删除)内找任意个数,使异或和最大

解析

异或和最大值,明显的线性基,但是线性基只要insert,而没有erase,所有我们离线处理删除操作,正向删除相当于反向插入,这样基本思路就有了

接下来要处理完整区间的问题

把每个位置都放一个线性基,每插入一个点,如果左右连着一段已经处理出来的有效区间,那么就可以合并到一起

怎么合并,和哪个合并?

每插入一个点,那个点接壤的点如果是已有区间的一部分,那么就一定是端点。
所以就可以用两个端点的相关值来表示这个区间的相关值
相关值有:左端点和右端点位置,一个区间的线性基

那么就用l[i]表示i所在区间的左端点位置,r[i]同理
insert 位置p时,如果连着p-1,就可以通过l[p-1]知道这个区间的左端点位置

代码


#include<bits/stdc++.h>
using namespace std;
struct linear_Bace;
typedef linear_Bace LB;
#define N 100009

struct linear_Bace{
    int a[33];
    void insert(int val){
        for(int i=31;i>=1;i--){
            int j=i-1;
            if(val&(1<<j)){
                if(!a[i]){
                    a[i]=val;break;
                }
                val^=a[i];
            }
        }
        if(val==0)a[0]=1;//可以通过异或变成0
    }
    int fin(int val){
        if(val==0&&a[0])return 1;
        for(int i=31;i>=1;i--){
            int j=i-1;
            if(val&(1<<j)){
                val^=a[i];
                if(!val)return 1;
            }
        }
        return 0;
    }
    int finmax(){
        int ans=0;
        for(int i=31;i>=1;i--){
            if((ans^a[i])>ans)ans^=a[i];
        }
        return ans;
    }
    int finmin(){
        if(a[0])return 0;
        for(int i=1;i<=31;i++){
            if(a[i])return a[i];
        }
        return 0;
    }
}e[N];

void merge(LB &a,LB &b){//a和b都变成a+b
    for(int i=31;i>=1;i--){
        if(b.a[i]==0)continue;
        a.insert(b.a[i]);
    }
    b=a;
}

int nu[N];
int ord[N];
int f[N],l[N],r[N];

int main(){
    int n;scanf("%d",&n);
    for(int i=1;i<=n;i++)l[i]=r[i]=i,f[i]=0;
    for(int i=1;i<=n;i++)scanf("%d",nu+i);
    for(int i=1;i<=n;i++)scanf("%d",ord+i);

    int ans=0;
    stack<int>A;
    for(int i=n;i>=1;i--){
        int p=ord[i],y=nu[p];
        f[p]=1;
        if(f[p-1]==0&&f[p+1]==0){//孤立点
            e[p].insert(y);
            ans=max(ans,e[p].finmax());
        }
        else if(f[p-1]&&f[p+1]){//连接两个区间的点
            int u=l[p-1],v=r[p+1];
            r[u]=v,l[v]=u;
            e[v].insert(y);
            merge(e[v],e[u]);
            ans=max(ans,e[v].finmax());
        }
        else if(f[p-1]){//连接左区间
            int u=l[p-1];
            e[u].insert(y);
            r[u]=p,l[p]=u;
            e[p]=e[u];
            ans=max(ans,e[u].finmax());
        }
        else{//连接右区间
            int u=r[p+1];
            e[u].insert(y);
            r[p]=u,l[u]=p;
            e[p]=e[u];
            ans=max(ans,e[u].finmax());
        }
        A.push(ans);
    }
    while(!A.empty()){
        printf("%d\n",A.top());A.pop();
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值