Loading

异或线性基

异或线性基

我们先从线性基的基本问题开始。

给定一个数组 \(A = [a_1, a_2, a_3, \dots, a_n]\),其中 \(a_i \le 2 ^ d\),在 \(A\) 中选择某个子集,将其中的所有元素异或,求出可以得到的数字的个数。

考虑一个简单的做法,我们用一个集合 \(S\) 表示当前可以凑出来的所有元素,那么,对于每一个 \(a_i\),我们将 \(S\) 中的每一个元素与 \(a_i\) 异或后的结果加入到 \(S\) 中,最终答案就是 \(S\) 的大小。

这样,我们就有了一个 \(O(n \times 2 ^ d)\) 的做法。


我们需要研究一下 \(S\) 的性质,假设 \(x, y\)\(S\) 中的两个元素,那么,显然的 \(x \oplus y\) 也存在与 \(S\) 中。

因为我们可以将凑出 \(x\) 的每一个元素与凑出 \(y\) 的每一个元素组合起来,凑出 \(x \oplus y\)

即使凑出 \(x, y\) 用到了相同的元素也没关系,因为两个相同的数异或是等于 \(0\) 的,也就是没有贡献,可以直接丢掉。

因此,我们假设 \(x\)\(S\) 中的某个元素,\(y\) 不是 \(S\) 中的某个元素,那么 \(x \oplus y\) 肯定也不是 \(S\) 中的元素。

因为如果 \(x \oplus y\)\(S\) 中的元素,我们就可以将凑出 \(x\) 的方案和凑出 \(x \oplus y\) 的方案组合到一起,凑出 \(y\)

因此,我们可以得出一个做法:

对于每一个 \(a_i\),判断 \(a_i\) 是否存在与 \(S\) 中,如果不存在,就将 \(S\) 中的所有元素与 \(a_i\) 异或,再加入 \(S\) 中。

显然的,每次我们的集合大小都会翻倍,也就是说,最多只会加倍 \(d\) 次。

这样的时间复杂度是 \(O(n + 2 ^ d)\)


但是,如果 \(2 ^ d\) 很大,这样是不行的,因为我们无法存储 \(S\),这意味着我们需要压缩 \(S\)

这时候,集合的大小最多只会加倍 \(d\) 次是十分重要的,我们可以只存储让集合大小翻倍的那些 \(a_i\) 即可。

但是,接下来又有一个问题:我们要如何判断当前的 \(a_i\) 是否可以被凑出来呢?

我们可以发现,我们所存储的这些 \(a_i\) 是可以进行某些变换的。

加入当前我们存储了 \(x, y\) 两个元素,我们把这两个元素变成 \(x, x \oplus y\) 也是不会影响这个集合所能凑出的元素的。

又由于我们最多只会存储 \(d\) 个元素,因此,我们可以考虑每一个元素管辖一位。

也就是说,我们假设存储的 \(b_i\) 的二进制表示下的最高位为 \(c_i\),那么,我们需要使得整个 \(b\) 数组的所对应的 \(c\) 数组中的元素互不相同。

我们每一次可以将 \(c_i\) 相等的所有数字,也就是 \(x_1, x_2, x_3, \dots, x_k\),把 \(x_2, x_3, \dots, x_k\) 全部异或上 \(x_1\),这样每一个 \(b_i\) 所对应的 \(c_i\) 就都是互不相同的了。

这样,时间复杂度就是 \(O(n \times d)\)\(O(n \times d \log d)\) 的。

int F(ll x) {
    for (int i = 49; i >= 0; i--) {
        if (x & (1ll << i)) return i;
    }
}

struct matrix {
    ll b[55];

    ll Check(ll x) {
        for (int k = 49; k >= 0; k--) {
            if (x & (1ll << k)) x ^= b[k];
        }
        return x;
    }

    void Insert(ll x) {
        ll tp = Check(x), t = 0;
        if (tp) t = F(tp);
        else return ;
        while (b[t]) tp ^= b[t], t = F(tp);
        b[t] = tp;
    }
};

其实感觉有点像 abc 236 f 的时间复杂度的证明。

abc 141 f

题意

给定 \(n\) 个非负整数 \(a_1, a_2, \dots, a_n\),将这些数分为 \(A, B\) 两个集合(不能为空),使得 \(A\) 集合内元素异或值加上 \(B\) 集合内元素异或值最大。

求出最大值。

思路

我们假设当前分到 \(A\) 集合中的数的异或值为 \(x\),所有数的异或值为 \(y\)

那么,我们就是要求出 \(x + (x \oplus y)\) 的最大值。

我们考虑 \(y\) 的每一位,假设当前是第 \(i\) 位:

  • 如果当前这一位为 \(1\),那么 \(x\) 的这一位无论是 \(1\),还是 \(0\)\(x, x \oplus y\) 的这一位的总和都是 \(1\),也就是说这一位的贡献永远是 \(2 ^ i\)
  • 如果当前这一位为 \(0\),那么,我们肯定希望 \(x, x \oplus y\) 在这一位都是 \(1\),但是,显然的,我们很难做到让这样的每一位的贡献都是 \(2 \times 2 ^ i\)

所以,我们只考虑 \(y\)\(0\) 的每一位,求出这些位的异或最大值。

代码

#include <bits/stdc++.h>

using namespace std;
using ll = long long;

const int N = 1e5 + 10;

int n, m;
ll a[N], s, b[N], ans;

int main() {
    ios::sync_with_stdio(0), cin.tie(0);
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i], s ^= a[i];

    for (int i = 1; i <= n; i++) {
        ll x = a[i] & (s ^ ((1ll << 60) - 1));
        sort(b + 1, b + m + 1);
        for (int j = m, k = 60; j >= 1; ) {
            for (; k >= 0 && !(b[j] >> k); k--);
            ll t = b[j];
            for (j--; j >= 1 && (b[j] >> k) == 1; b[j] ^= t, j--);
        }

        sort(b + 1, b + m + 1);
        for (int k = 60, j = m; k >= 0; k--) {
            if (x & (1ll << k)) {
                for (; j >= 1 && (b[j] >> k) > 1; j--);
                if ((b[j] >> k) == 1) x ^= b[j];
            }
        }

        if (x) b[++m] = x;
    }

    sort(b + 1, b + m + 1);
    for (int i = 60, j = m; i >= 0; i--) {
        if (ans & (1ll << i)) continue;
        for (; j >= 1 && (b[j] >> i) > 1; j--);
        if ((b[j] >> i) == 1) ans ^= b[j];
    }
    cout << ans + (s ^ ans);
    return 0;
}

CF1902F

题意

给定一颗包含 \(n\) 个结点的树,每个点有一个点权 \(a_i\)

\(q\) 次询问,每次查询给出 \(x, y, k\),请你判断 \(x\)\(y\) 的路径上是否可以选出一个子集,使得子集的异或和为 \(k\)

思路

我们首先得明确一个点:线性基是可以直接合并的

所以,我们可以每次求出 lca,在求出 lca 的过程中将线性基的倍增数组合并即可。

但是这样常数太大了,所以我们可以用类似于 ST 表的方式求出答案。

对于这样的一条链,我们可以把它拆分为两个部分:

像这样查询即可,时间复杂度为 \(O(q \log ^ 3 n)\)

代码

#include <bits/stdc++.h>

using namespace std;
using ll = long long;

const int N = 2e5 + 10;

int fir[1 << 20];

struct matrix {
    int b[25];

    int Check(int x) {
        for (int k = 19; k >= 0; k--) {
            if (x & (1 << k)) x ^= b[k];
        }
        return x;
    }

    void Insert(int x) {
        int tp = Check(x), t = 0;
        if (tp) t = fir[tp];
        else return ;
        while (b[t]) tp ^= b[t], t = fir[tp];
        b[t] = tp;
    }
} tr[N][18], Empty;

int n, a[N], fa[N][18], q, dep[N], Log[N];
vector<int> g[N];

void dfs(int u, int fx) {
    tr[u][0] = Empty, tr[u][0].Insert(a[u]);
    fa[u][0] = fx, dep[u] = dep[fx] + 1;
    for (int v : g[u]) {
        if (v != fx) dfs(v, u);
    }
}

matrix Merge(matrix x, matrix y) {
    for (int i = 0; i < 20; i++) x.Insert(y.b[i]);
    return x;
}

inline int lowbit(int x) {
    return x & (-x);
}

int Find(int x, int k) {
    while (k) x = fa[x][Log[lowbit(k)]], k -= lowbit(k);
    return x;
}

int Lca(int x, int y) {
    if (dep[x] < dep[y]) swap(x, y);
    x = Find(x, dep[x] - dep[y]);
    if (x == y) return x;
    for (int i = 17; i >= 0; i--) {
        if (fa[x][i] != fa[y][i]) {
            x = fa[x][i], y = fa[y][i];
        }
    }
    return fa[x][0];
}

matrix query(int x, int y) {
    int p = Log[dep[x] - dep[y] + 1];
    return Merge(tr[x][p], tr[Find(x, dep[x] - dep[y] + 1 - (1 << p))][p]);
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0);
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    for (int i = 2; i <= n; i++) Log[i] = Log[i / 2] + 1;
    for (int i = 0; i < (1 << 20); i++) {
        for (int j = 19; j >= 0; j--) {
            if (i & (1 << j)) {
                fir[i] = j; break;
            }
        }
    }
    fill(Empty.b, Empty.b + 25, 0);
    for (int i = 1, u, v; i < n; i++) {
        cin >> u >> v;
        g[u].push_back(v), g[v].push_back(u);
    }
    dfs(1, 0);
    for (int i = 1; i < 18; i++) {
        for (int j = 1; j <= n; j++) {
            fa[j][i] = fa[fa[j][i - 1]][i - 1];
            tr[j][i] = Merge(tr[j][i - 1], tr[fa[j][i - 1]][i - 1]);
        }
    }
    cin >> q;
    while (q--) {
        int x, y, k; cin >> x >> y >> k;
        int t = Lca(x, y);
        matrix ans = Merge(query(x, t), query(y, t));
        cout << (ans.Check(k) ? "NO\n" : "YES\n");
    }
    return 0;
}
posted @ 2024-09-20 17:55  chengning0909  阅读(6)  评论(0编辑  收藏  举报