学习线性基 bzoj 4568 幸运数字

好久没写博客了,最近学习了一下新姿势:线性基,这个就很厉害了。

线性基其实很好理解。

会线性基可以直接跳过下面的部分。。。

我们先抛出一道简单的题:
给你一个长度为n的序列,在其中任选数字求可以得到的最大异或和。

我们知道任意两个,和连续一段的最大异或和,可以用Trie加贪心水掉,但这道题怎么做呢?

线性基就可以用来处理这个玩意。

通俗的讲:线性基其实是一些数的集合,而用这些集合里的数字任意组合异或可以表示完原数列中所有数字异或的情况。

而线性基中的数字有个重要性质就是 不会存在有两个数他们的二进制最高位相同。
所以线性基集合大小最多不会超过原数列中最大数的二进制位数。这个是显然的。

让我们先来讲如何构造线性基集合,考虑对于一个数x,如果x最高位在线性基集合中没有的话,就直接扔进去,否则就把x异或上那个存在的数,一直进行下去。

这是代码:其中Greed数组就表示了线性基集合。

inline void Greed_insert(ll x) {
    if( !x ) return;
    for(int i = 61; i >= 0; i--) 
    if(x & (1LL<<i)) {
        if( !Greed[i]) {
            Greed[i] = x;
            break;
        }
        else x ^= Greed[i];
    }
}

我们再来讲讲它为什么可以表示完原来序列的所有情况。

假如我们要加入b,它的最高位是i,原来在这一位上已经存在一个数a了,那么我们令c=b^a,如果我们把c加进去c^a就可以表示出原来的b了所以我们加入c进去不会缺失所有异或的情况(也就是说可以凑出a,b,a^b)。

是不是非常easy。

让我们回到这道题上面:

这道题题意其实就是:我们把原来序列上的问题转换到了树上来做,现在每次询问问你u到v路径上的最大异或和。
其实做法差不多,只需要加一个st表,每次询问暴力合并线性基就可以了(一个一个插进去)。

ac代码,可能有点丑。

#include <cstdio>
#include <algorithm>
#include <vector>
#include <cstring>
#include <iostream>
using namespace std;

#define N 20005
#define ll long long

#ifdef WIN32 
#define AUTO "%I64d"
#else 
#define AUTO "%lld"
#endif

struct Edge {
    int v, next;
};
Edge e[2 * N];

int n, m, fa[N][20], a, b, num, head[N], dep[N];
ll aa[N], XOR[N][20][62];

inline void adde(int i, int j) {
    e[++num].v = j;
    e[num].next = head[i];
    head[i] = num;
}
inline void insert(ll x, int u, int k) {
    if( !x ) return;
    for(int i = 61; i >= 0; i--) 
    if(x & (1LL<<i)) {
        if( !XOR[u][k][i]) {
            XOR[u][k][i] = x;
            break;
        }
        else x ^= XOR[u][k][i];
    }
}
inline void dfs(int u) {
    for(int i = head[u]; i; i = e[i].next) {
        int v = e[i].v;
        if(v == fa[u][0]) continue;
        fa[v][0] = u;
        insert(aa[u], v, 0);
        insert(aa[v], v, 0);
        dep[v] = dep[u] + 1;
        dfs(v);
    }
}
ll Greed[62];
inline void Greed_insert(ll x) {
    if( !x ) return;
    for(int i = 61; i >= 0; i--) 
    if(x & (1LL<<i)) {
        if( !Greed[i]) {
            Greed[i] = x;
            break;
        }
        else x ^= Greed[i];
    }
}
ll _Greed() {
    ll rt = 0;
    for(int i = 61; i >= 0; i--)
        if( (rt ^ Greed[i]) > rt ) rt ^= Greed[i];
    return rt;
}
ll solv(int u, int v) {
    if(u == v) return aa[u];
    if(dep[u] < dep[v]) swap(u, v);
    int len = dep[u] - dep[v];
    for(int p = 0; len; p++, len >>= 1) {
        if(len & 1) {
            for(int k = 0; k <= 61; k++)
            Greed_insert(XOR[u][p][k]);
            u = fa[u][p];
        }
    }
    if( u == v ) return _Greed();
    for(int j = 18; j >= 0; j--) {
        if(fa[u][j] == fa[v][j]) continue;
        for(int k = 0; k <= 61; k++)
        Greed_insert(XOR[u][j][k]),
        Greed_insert(XOR[v][j][k]);
        u = fa[u][j];
        v = fa[v][j];
    }
    for(int k = 0; k <= 61; k++)
    Greed_insert(XOR[u][0][k]),
    Greed_insert(XOR[v][0][k]);
    return _Greed();
}
int main() {
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++)
    scanf(AUTO, &aa[i]);
    for(int i = 1; i < n; i++) {
        scanf("%d%d", &a, &b);
        adde(a, b);
        adde(b, a);
    }
    fa[1][0] = 1;
    dfs(1);
    for(int j = 1; j <= 18; j++) {
        for(int i = 1; i <= n; i++) {
            fa[i][j] = fa[fa[i][j-1]][j-1];
            for(int k = 0; k <= 61; k++) {
                insert(XOR[i][j-1][k], i, j);
                insert(XOR[fa[i][j-1]][j-1][k], i, j);
            }
        }
    }

    while(m--) {
        memset(Greed, 0, sizeof Greed);
        scanf("%d%d", &a, &b);
        printf(AUTO"\n", solv(a, b));
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值