Site-T

SUP-project


  • Home
  • Archive
  • Tags
  •  

© 2019 Time Looper

Theme Typography by Makito

Proudly published with Hexo

sup-extra 线性基

Posted at 2019-08-02 ICPC 

sup-extra 线性基及部分例题

1.先来讲讲什么是线性基吧

​ 线性基是专门用来处理异或相关问题的一个数的集合,把原先的数的集合中挑出一部分数,利用这些挑出来的数进行相互的异或运算就能得到原先的数。不过实际上这些数能够组合出的数字会比原集合的数字相等或更多,基于这个方面的原因,只有当不考虑集合本来的数而只考虑异或结果的时候会用到线性基。

2.原理

​ 那么具体的原理是怎么个原理呢?考虑一下二进制数的性质,当一个数组中存储n个高位均不相同的数字的时候,能够通过异或得到多少个数呢?答案很明显是2n,因为显然一个有k位的数只能影响从小到大k位,所以从高位往低位遍历的时候,针对一个数选与不选肯定会得到两个不同的结果,所以最大为64位的数的集合,我们构造线性基的时候只需要选出64个不同的数字就好。

​ 当然光是讲一下构造的原理并不能实际解决问题。针对具体的问题就结合题目来看吧。

3.例题及代码

求异或最大值 bzoj 2460

题意:给你n个石头的编号和权值,让你求出在石头编号异或值最大的情况下能得到的权值。很明显就是要求编号的异或最大值。

代码:

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<vector>
using namespace std;
typedef long long LL;
const int maxn=100005;
struct mine{
    LL num;
    LL val;
}ori[maxn];
bool comp(mine a,mine b){
    return a.val>b.val;
}
LL lis[70];
bool havezero;
void init(){
    memset(lis,0,sizeof(lis));
    havezero=false;
}
bool build(LL x){
    for(int i=62;i>=0;--i){
        if(x&(1ll<<i)){
            if(lis[i])
                x^=lis[i];
            else {lis[i]=x;return true;}
        }
    }
    havezero=true;
    return false;
}
int main(void)
{
    int n;
    while(~scanf("%d",&n)){
            init();
        for(int i=0;i<n;++i){
            scanf("%lld%lld",&ori[i].num,&ori[i].val);
        }
        sort(ori,ori+n,comp);
        LL res=0;
        for(int i=0;i<n;++i){
            res+=ori[i].val;
            if(build(ori[i].num)==false){
                res-=ori[i].val;
            }
        }
        printf("%lld\n",res);

    }
    return 0;
}

求异或第k小 hdu 3949

版题就不谈思路了,第k小求的时候要首先重新处理线性基,使得每位数字都是最小的,这样能够提供的贡献就是2m,然后就可以得到第k小了。

代码:

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<vector>
using namespace std;
typedef long long LL;
const int maxn=100005;
const LL INFL=0x3f3f3f3f3f3f3f3f;
int t,n,q,coun;
LL lis[70],sml[70];
bool havezero;
void init(){
    memset(lis,0,sizeof(lis));
    havezero=false;
}
bool build(LL x){
    for(int i=62;i>=0;--i){
        if(x&(1ll<<i)){
            if(lis[i]) x^=lis[i];
            else {
                lis[i]=x;
                return true;
            }
        }
    }
    havezero=true;
    return false;
}
bool rebuild(){
    coun=0;
    for(int i=62;i>=1;--i){//i=0的时候必定为1,不用往下异或了
        if(lis[i]){
            for(int j=i-1;j>=0;--j){
                if(lis[i]&(1ll<<j)){//保证有减小
                    lis[i]^=lis[j];
                }
            }
        }
    }//由于线性基性质+每次只改变后面j位,所以扫一遍就够了
    for(int i=0;i<=62;++i){
        if(lis[i])
            sml[coun++]=lis[i];
    }
}
LL ksmall(int k){
    if(havezero) --k;
    LL res=0;
    if(k>=(1ll<<coun)) return -1;
    for(int i=coun;i>=0;--i){
        if(k&(1ll<<i))
            res^=sml[i];
    }
    return res;
}
int main(void)
{
    scanf("%d",&t);
    for(int mark=1;mark<=t;++mark){
        scanf("%d",&n);
        LL mid;
        init();
        for(int i=0;i<n;++i){
            scanf("%lld",&mid);
            build(mid);
        }
        rebuild();
        scanf("%d",&q);
        printf("Case #%d:\n",mark);
        for(int i=0;i<q;++i){
            scanf("%lld",&mid);
            printf("%lld\n",ksmall(mid));
        }
    }
    return 0;
}

xor bzoj 2115

题意:要你找出一条从1到n的路径,使得路径上的权值异或和最大。

则实际上就是任求一条1到n的路径然后再去得到每个环所能提供的权值,因为走到环里转一圈再回到1点的时候得到的权值就是环的异或和。同时如果有多条到n的路径,则他们之间也能构成环,在跟起始选择的一条进行各种异或后肯定能得到最长的。

代码:

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<vector>
using namespace std;
typedef long long LL;
const int maxn=100005;
struct edge{
    int to,num;
    LL val;
    edge(int t,LL v,int n):to(t),val(v),num(n){}
    edge(){}
};
vector<edge> tree[maxn];
int n,m,tot;
LL dis[maxn],circle[maxn*3];
bool vis[maxn];
void addedge(int u,int v,LL val,int num){
    tree[u].push_back(edge(v,val,num));
    tree[v].push_back(edge(u,val,num));
}
void dfs(int u,int fa){
    vis[u]=true;
    for(int i=0;i<tree[u].size();++i){
        int v=tree[u][i].to,num=tree[u][i].num;
        LL val=tree[u][i].val;
        if(num!=fa){
            if(!vis[v]){
                dis[v]=dis[u]^val;
                dfs(v,num);
            }
            else{
                circle[++tot]=dis[u]^dis[v]^val;
            }
        }
    }
}
LL xmxyji[64];
bool xmxyji_insert(LL k){
    for(int i=63;i>=0;--i){
        if(k&(1ll<<i)){
            if(!xmxyji[i]){
                xmxyji[i]=k;
                return true;
            }
            else{
                k^=xmxyji[i];
            }
        }
    }
    return false;
}
LL getres(){
    LL res=dis[n];
    for(int i=63;i>=0;--i){
        if((res^xmxyji[i])>res)
            res^=xmxyji[i];
    }
    return res;
}
int main(void)
{
    while(~scanf("%d%d",&n,&m)){
        for(int i=1;i<=n;++i){
            tree[i].clear();
        }
        memset(dis,0,sizeof(dis));
        memset(vis,0,sizeof(vis));
        int a,b;
        LL c;
        tot=0;
        for(int i=1;i<=m;++i){
            scanf("%d%d%lld",&a,&b,&c);
            addedge(a,b,c,i);
        }
        dfs(1,0);
        memset(xmxyji,0, sizeof(xmxyji));
        for(int i=1;i<=tot;++i)
            xmxyji_insert(circle[i]);
        LL res=getres();
        printf("%lld\n",res);
    }
    return 0;
}

XOR UVALive-8512

这个题要求区间异或最值,实际上对于每个插入的数字,我们都做一个线性基,并且记录每个数字是第几位的。当有更靠前的数字插入进来时优先替换旧的,这样在求l r区间上的结果时就可以直接遍历r的线性基,把其中位置在l-1及之后的点都抛弃掉。这样就能直接得到结果了。

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<vector>
using namespace std;
typedef long long LL;
const int maxn=100005;
LL xmxyji[maxn][64];
int posi[maxn][64];
LL k_seperate[64],tot,n,q,k;
void build(int num,LL val){
    for(int i=0;i<tot;++i){
        if(val&k_seperate[i]){
            val-=k_seperate[i];
        }
    }
    for(int i=0;i<64;++i){
        xmxyji[num][i]=xmxyji[num-1][i];
        posi[num][i]=posi[num-1][i];
    }

    int nown=num;
    for(int i=63;i>=0;--i){
        if(val&(1<<i))
            if(xmxyji[num][i]){
                if(posi[num][i]<nown){
                    swap(xmxyji[num][i],val);
                    swap(posi[num][i],nown);
                }
                val^=xmxyji[num][i];
            }
            else{
                xmxyji[num][i]=val;
                posi[num][i]=nown;
                return;
            }

    }
}
LL query(int l,int r){
    LL res=0;
    for(int i=63;i>=0;--i){
        if(posi[r][i]>=l){
            if((xmxyji[r][i]^res)>res){
                res^=xmxyji[r][i];
            }
        }
    }
    return res|k;
}
int main(void)
{
    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%lld%lld%lld",&n,&q,&k);
        tot=0;
        LL tmp=k;
        while(tmp){
            k_seperate[tot++]=tmp&(-tmp);
            tmp-=tmp&(-tmp);
        }
        memset(xmxyji,0,sizeof(xmxyji));
        memset(posi,0,sizeof(posi));

        for(int i=1;i<=n;++i){
            scanf("%lld",&tmp);
            build(i,tmp);
        }
        for(int i=0;i<q;++i){
            int a,b;
            scanf("%d%d",&a,&b);
            printf("%lld\n",query(a,b));
        }
    }
    return 0;
}

Share 

 Previous post: sup-005 动态博客搭建 Next post: sup-extra 代码模板 

© 2019 Time Looper

Theme Typography by Makito

Proudly published with Hexo