线性基学习笔记

粘链接:

[入门]https://www.cnblogs.com/ljh2000-jump/p/5869991.html
[题]https://www.cnblogs.com/vb4896/p/6149022.html

性质:

1:线性基的任意一个非空子集 x o r xor xor起来都不是0。证明可以考虑一定存在一个最高位,使得有且仅有一个。

2:一些数能够 x o r xor xor出来0,当且仅当它们的线性基大小小于原来的集合大小。考虑线性基的本质实际上就是对彼此能异或出来的那些数合并了。如果合并不了,说明原来彼此不可替代,也就不会 x o r xor xor出来0啦。

3:线性基的彼此 x o r xor xor仍然是一组基

题:

1:板子题,查询一些数的子集的xor和的最大值。

  • meet-in-the-middle。插到trie里面。 O ( 2 n 2 ∗ w ) O(2^{\frac{n}{2}}*w) O(22nw)
  • 线性基板子 O ( n l o g n ) O(nlogn) O(nlogn)

2:查询一个数是否可以子集被 x o r xor xor出来

  • 从高位向低位考虑。如果高位是1,一定要选,否则没法表示。这个时候后面位的状态都要 x o r xor xor当前选的这个数的。一旦当前的考虑的数为0了,就代表表示成功。结束

3:hdu3949经典问题,问数的子集中 x o r xor xor出来的数第 k k k小。
对求出来的一组线性基,模仿高斯-约旦消元法。对于每一对 a i a_i ai a j ( i > j ) a_j(i>j) aj(i>j) a i = a i x o r a j a_i =a_i xor a_j ai=aixoraj。这就保证了对于每一位,只有1个数唯一存在。对k二进制拆分,可以知道哪些位置必须存在。 注意要对bas重新填位。因为是第k小。

4:bzoj2460
按照权值从大到小贪心。维护当前的线性基来表示当前的id可否被之前的所表示,如果能就GG,否则就加。

5:bzoj2115
经典问题,给一张无向连通图。问1到N的异或值最大的路径。

1:任意一条路径上都可以加一个环而不改变其他的边。 考虑从路径上的一个点出发,遍历这个环再回去。这样出来的这条路径被经过两次就抵消了。只加了这个环。

2: 一些简单环的线性组合一定可以凑出任何一个复杂环。这个就随便画两个环,根据路径抵消发现它们形成了一个更大的环。归纳一下?

做法: 从1开始建立出图的dfs树。记录 d i s i dis_i disi代表1到i的路径上的异或和。对于返祖边,根据 d i s [ u ] x o r d i s [ v ] x o r w [ i ] dis[u] xor dis[v] xor w[i] dis[u]xordis[v]xorw[i] 把这个简单环拿出来。对所有简单环,求一个线性基。以 dis[n]为原状态,从高位到低位不断尝试log个线性基看是否会更大。如果更大就加上。

6:cf724G
给定一张无向图,问有多少个三元组(u,v,z)。满足u到v存在路径的异或和为z,求z的和。

1:每个连通块单独考虑再加起来
2:拆开位考虑计算贡献。根据上一题,我们知道如果确定了两个点(u,v)的话。u到v的任何一条路径都可以通过(u,v)的一条简单路径xor上某些环得到。

做法1: 枚举两个点(u,v),然后对于每一位。考虑为dis[u]^dis[v],如果这位为1,那么线性基一定要这位不是1。否则一定要这位是1。我们搞出来那种位与位之间互不相同的线性基。就相当于强制一位选or不选,剩下的位瞎选就好了。 O ( n 2 ∗ 64 ∗ l o g n ) O(n^2*64*logn) O(n264logn)

做法2:因为对于一个点x,dis[x]是固定不变的。我们考虑只固定一个点x,对于每一位都记录下有a个点的dis位0,有b个点的dis为1。 分四种情况考虑:

  • x的这一位为1,线性基里面有1。要么3个都是1,要么线性基那位不选,y的那位选0。
  • x的这位为1,线性基里面没有1。那么线性基直接瞎选,y的那位一定选0。
  • x的这位为0,线性基里面有1.要么只有线性基选择1,其他两个是0.只有另一个选了1,线性基和x都是0.
  • x的这位为0,线性基里面没有1。那么线性基直接瞎选,y的那位一定选1。

这里的x,y是不限制顺序的(抛掉了本身),所有都被重复计算了2遍。最后除以二就好了。

O ( n ∗ 64 ∗ l o g n ) O(n*64*logn) O(n64logn)可以通过预处理2的幂次来省下里面的一个 l o g log log

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 5, M = 2e5 + 5, W = 65, P = 1e9 + 7;
int head[N], nxt[M<<1], v[M<<1], vis[N], a[N], tot;
LL w[M<<1], bas[W], pw[W], f[M<<1], dis[N], inv2;
int n, m, siz, cnt, num;
inline LL read() {
    LL x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-')f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}
inline void add(int x, int y, LL z) {
    v[++tot] = y;
    w[tot] = z;
    nxt[tot] = head[x];
    head[x] = tot;
}
inline LL q_p(LL a, LL b, LL p) {
    LL res = 1;
    while (b) {
        if (b & 1) res = res * a % p;
        a = a * a % p;
        b >>= 1;
    }
    return res;
}
inline void dfs(int x, int fa) {
    vis[x] = 1; a[++siz] = x;
    for (int i = head[x]; i; i = nxt[i]) {
        int y = v[i];
        if (y == fa) continue;
        if (!vis[y]) {
            dis[y] = dis[x] ^ w[i];
            dfs(y, x);
        }
        else f[++cnt] = dis[x] ^ dis[y] ^ w[i];
    }
}
inline void guass() {
    num = 0;
    for (int i = 1; i <= cnt; ++i) {
        for (int j = 63; j >= 0; --j) {
            if ((f[i] >> j) & 1) {
                if (!bas[j]) {bas[j] = f[i]; break;}
                else f[i] ^= bas[j];
            }
        }
    }
    //for (int i = 0; i <= 63; ++i) if (bas[i])num++;
    for (int i = 63; i >= 0; --i) {
        for (int j = i - 1; j >= 0; --j) {
            if (bas[i] && bas[j] && ((bas[i] >> j) & 1)) bas[i] ^= bas[j];
        }
        if (bas[i]) num++;
    }
}
inline void upd(LL &a, LL b) {
    a += b;
    if (a >= P) a -= P;
}
int cnt1[W];
inline LL solve() {
    LL res = 0;
    memset(cnt1, 0, sizeof cnt1);
    for (int i = 1; i <= siz; ++i) {
        for (int j = 63; j >= 0; --j) {
            if ((dis[a[i]] >> j) & 1) cnt1[j]++;
        }
    }
    int flag;
    for (int k = 0; k <= 63; ++k) {
        flag = 0;
        for (int i = 0; i <= 63; ++i) {
            if ((bas[i] >> k) & 1) flag = 1;
        }
        for (int i = 1; i <= siz; ++i) {
            if ((dis[a[i]] >> k) & 1) {
                if (flag) upd(res, pw[num - 1] * pw[k] % P * (siz - 1) % P);
                else upd(res, pw[num] * pw[k] % P * (siz - cnt1[k]) % P);
            }
            else {
                if (flag) upd(res, pw[num - 1] * pw[k] % P * (siz - 1) % P);
                else upd(res, pw[num] * pw[k] % P * cnt1[k] % P);
            }
        }
    }
    return res * inv2 % P;
}
int main() {
    //freopen("data.out", "w", stdout);
    n = read(); m = read();
    for (int i = 1; i <= m; ++i) {
        int x = read(), y = read();
        LL z = read();
        add(x, y, z); add(y, x, z);
    }
    inv2 = q_p(2, P - 2, P);
    pw[0] = 1; LL ans = 0;
    for (int i = 1; i < W; ++i) pw[i] = pw[i - 1] * 2 % P;
    for (int i = 1; i <= n; ++i) {
        if (!vis[i]) {
            memset(bas, 0, sizeof bas);
            cnt = 0; siz = 0;
            dfs(i, i);
            guass();
            upd(ans, solve());
        }
    }
    printf("%I64d\n", ans);
    return 0;
}

https://www.cnblogs.com/WR-Eternity/p/10291631.html

注意这里要把每个连通块内的点单独拿出来。如果写一般线性基的话,只需要判断是否存在一位的线性基,它的j位是1就可以了。

7 bzoj4568
线性基合并就暴力合并:

inline void merge(LL *a, LL *b) {// 把b合并到a里
    for (int i = 63; i >= 0; --i) {
        LL x = b[i];
        if (x)
        for (int j = 63; j >= 0; --j) {
            if ((x >> j) & 1) {
                if (!a[j]) {a[j] = x; break;}
                else x ^= a[j];
            }
        }
    }
}

1:树剖套线性基合并 O ( n l o g 2 n ∗ 6 4 2 ) O(nlog^2n*64^2) O(nlog2n642) 这个东西在Bzoj上面水过去了,在Loj上90pts。

2:树上倍增+线性基合并。 O ( n l o g n ∗ 6 4 2 ) O(nlogn*64^2) O(nlogn642) 对于每个点预处理它向上 2 j 2^j 2j步的线性基。预处理和运算的时候同样套一个线性基合并。

#include <bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N = 2e4 + 5, W = 66, K = 16;
LL a[N], bas[N][K][W], cur[N];
int f[N][K], dep[N];
int head[N], nxt[N<<1], v[N<<1], tot;
inline void add(int x, int y) {
    v[++tot] = y;
    nxt[tot] = head[x];
    head[x] = tot;
}
inline LL read() {
    LL x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-')f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}
int n, q;
inline void merge(LL *a, LL *b) {
    for (int i = 0; i <= 63; ++i) {
        LL x = b[i];
        if (x)
        for (int j = 63; j >= 0; --j) {
            if ((x >> j) & 1) {
                if (!a[j]) {a[j] = x; break;}
                else x ^= a[j];
            }
        }
    }
}
inline void dfs(int x, int fa) {
    for (int i = 1; i <= 15; ++i) {
        if (dep[x] < (1 << i)) break;
        f[x][i] = f[f[x][i - 1]][i - 1];
        merge(bas[x][i], bas[x][i - 1]);
        merge(bas[x][i], bas[f[x][i - 1]][i - 1]);
    }
    for (int i = head[x]; i; i = nxt[i]) {
        int y = v[i];
        if (y == fa) continue;
        dep[y] = dep[x] + 1;
        f[y][0] = x;
        for (int j = 63; j >= 0; --j) {
            if ((a[y] >> j) & 1) {
                if (!bas[y][0][j]) {bas[y][0][j] = a[y]; break;}
                else a[y] ^= bas[y][0][j];
            }
        }
        dfs(y, x);
    }
}
inline void debug(LL *a) {
    printf("\n");
    for (int i = 0; i <= 6; ++i) printf("%lld ", a[i]);
    printf("\n\n");
}
inline int LCA(int x, int y) {
    if (dep[x] < dep[y]) swap(x, y);
    int t = dep[x] - dep[y];
    for (int i = 0; i <= 15; ++i) {
        if ((t >> i) & 1) {
            merge(cur, bas[x][i]);
            x = f[x][i];
        }
    }
    for (int i = 15; i >= 0; --i) {
        if (f[x][i] != f[y][i]) {
            merge(cur, bas[x][i]);
            merge(cur, bas[y][i]);
            x = f[x][i]; y = f[y][i];
        }
    }
    if (x != y) {
        merge(cur, bas[x][0]);
        merge(cur, bas[y][0]);
        merge(cur, bas[f[x][0]][0]);
        return f[x][0];
    }
    else {
        merge(cur, bas[x][0]);
        return x;
    }
}
int main() {
    //freopen("loj2013.in", "r", stdin);
    //freopen("loj2013.out", "w", stdout);
    n = read(); q = read();
    for (int i = 1; i <= n; ++i) a[i] = read();
    for (int i = 1; i < n; ++i) {
        int x = read(), y = read();
        add(x, y); add(y, x);
    }
    for (int i = 63; i >= 0; --i) {
        if ((a[1] >> i) & 1) {
            if (!bas[1][0][i]) {bas[1][0][i] = a[1]; break;}
            else a[1] ^= bas[1][0][i];
        }
    }
    dfs(1, 1);
    for (int i = 1; i <= q; ++i) {
        int x = read(), y = read();
        memset(cur, 0, sizeof cur);
        LCA(x, y);
        LL ans = 0;
        for (int j = 63; j >= 0; --j) {
            if ((ans ^ cur[j]) > ans) ans ^= cur[j];
        }
        printf("%lld\n", ans);
    }
    return 0;
}


还是T掉了,可能我常数太大了吧。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值