线性基理解

个人理解:线性基有点类似于矩阵中向量组的最大无关组,一个数集V所有可能的异或结果是数集U。在数集U中,选出若干个数构成集合S,使得U中的每一个数字都可以由S中的若干元素通过异或来表示,选出的集合S就是数集V的线性基(也是U​​​​​​​的线性基)。 

选出集合S的过程通过以下函数来完成:

ll b[70];
void add(ll x){
    for(int i = 63; i >= 0 && x;i--)
        if((x>>i)&1){
            if(b[i] == 0){
                b[i] = x; return;
            }
            else x ^= b[i];
        }
}

V中的每个元素使用add函数调用。得到数组b,数组b就是集合V的线性基,其中b[i]不为0的位置表示该位置是任意可随意设置为0或1的(异或结果共2^count个值,count=Σ[b[i]!=0])。

引入问题1:P3812 【模板】线性基

题意:在一堆数种选出若干个数,使得选出来的数异或起来最大。

解决方案:找出线性基,对线性基能表示的高位尽量表示出来,由高位向低位枚举贪心即可。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n;  ll a[70], b[70], ans = 0;
void add(ll x){
    for(int i = 63; i >= 0 && x;i--)
        if((x>>i)&1){
            if(b[i] == 0){
                b[i] = x; return;
            }
            else x ^= b[i];
        }
}
int main(){
    scanf("%d",&n);
    for(int i = 1;i<=n;i++)
        scanf("%lld",&a[i]), add(a[i]);
    for(int i = 63;i>=0;i--)
        if(b[i])ans = max(ans,ans ^ b[i]);
    printf("%lld\n",ans);
    return 0;
}

应用问题2:P4301 [CQOI2013]新Nim游戏

题意:在第一个回合中,第一个游戏者可以直接拿走若干个整堆的火柴。可以一堆都不拿,但不可以全部拿走。第二回合也一样,第二个游戏者也有这样一次机会。从第三个回合(又轮到第一个游戏者)开始,规则和Nim游戏一样。

如果你先拿,怎样才能保证获胜?如果可以获胜的话,还要让第一回合拿的火柴总数尽量小。

解决方案:使得对方操作完剩余的数一定不为0,那么说明留给对方可操作的数一定线性无关,也就是留给对方一组线性基即可

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n;  ll a[500], b[70], sum = 0;
bool add(ll x){
    for(int i = 62; i >= 0 && x;i--){
        if((x>>i)&1){
            if(b[i] == 0){
                b[i] = x; return 1;
            }
            else x ^= b[i];
        }
    }
    return 0;
}
int main(){
    scanf("%d",&n);
    for(int i = 1;i<=n;i++)
        scanf("%lld",&a[i]), sum += a[i];
    sort(a+1,a+n+1,greater<ll>());
    for(int i = 1;i<=n;i++)
        if(add(a[i]))sum -= a[i];
    printf("%lld\n",sum);
    return 0;
}

应用问题3:P4151 [WC2011]最大XOR和路径

题意:考虑一个边权为非负整数的无向连通图,节点编号为 1 到 N,试求出一条从 1 号节点到 N 号节点的路径,使得路径上经过的边的权值的 XOR 和最大。路径可以重复经过某些点或边,当一条边在路径中出现了多次时,其权值在计算 XOR 和时也要被计算相应多的次数。

解决方案:先任意找到一条路径从1能够到达n,那么在这条路径上有若干个环可以让我们任意地选择走与不走,如果知道包含所有边的环的异或值,就是上面裸的问题1了,对于一条边(u,v),我们可以走 1 -> u -> v -> 1 的环,或是不走,这样对所有的边都有一个环,那么这些环全部都可以选择走与不走了,转化为问题1,AC

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n, m;
struct edge{
    ll w;
    int v, next;
}e[200005];
int head[50005],cnt;
void add(int u, int v, ll w){
    e[cnt].v = v, e[cnt].w = w, e[cnt].next = head[u], head[u] = cnt++;
}
ll dis[50005];
bool vis[50005];

ll b[64];
void addd(ll x){
    for(int i = 63;i>=0 && x;i--){
        if((x>>i)&1){
            if(!b[i]){
                b[i] = x;
                return;
            }else{
                x ^= b[i];
            }
        }
    }
}
int main(){
    memset(head,-1,sizeof head);
    scanf("%d%d",&n,&m);
    for(int i = 1;i<=m;i++){
        int u, v; ll w;
        scanf("%d %d %lld",&u,&v,&w);
        add(u,v,w); add(v,u,w);
    }
    queue<int>que;
    que.push(1); vis[1] = 1;
    while(que.size()){
        int u = que.front(); que.pop();
        for(int i = head[u];~i;i = e[i].next){
            if(!vis[e[i].v]){
                vis[e[i].v] = 1;
                que.push(e[i].v);
                dis[e[i].v] = (dis[u] ^ e[i].w);
            }
        }
    }
    for(int i = 0;i<m;i++)
        addd(dis[e[i<<1|1].v]^dis[e[i<<1].v]^e[i<<1].w);//1->u->v->1的异或和

    ll ans = dis[n];
    for(int i = 63;i>=0;i--)
        ans = max(ans, ans ^ b[i]);
    printf("%lld\n",ans);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值