线性基
线性基
定义:给定数集 S ,以异或运算张成的数集与 S 相同的极大线性无关集,称为原数集的一个线性基。
性质:
- 原数集的任意数均可通过线性基异或得到。
- 线性基里任意一个子集异或和非 0 。
- 一个数集可能有多个线性基,但是线性基大小唯一,且是满足性质一是大小最小的。
- 值域为 V 的数集的线性基大小上界为 logV 。
基本操作
记数组 p 表示数集的线性基,钦定若 pi 有值则 pi 的第 i 位为 1 且 >i 的位为 0 。
插入、查询存在性
从高到低枚举 k 的每一位,判断 pi 这个位置是否有数,没有就插入并退出,否则将 k 异或上 pi 继续判断(把最高位不断消掉)。
如果没有成功插入这个数,则说明这个数可以被线性基表示,无需插入。
时间复杂度 O(logV) ,不难发现插入操作同时可以用于查询一个数是否能被线性基表示。
inline bool insert(int k) { for (int i = B - 1; ~i; --i) if (k >> i & 1) { if (p[i]) k ^= p[i]; else { ++siz, p[i] = k; return true; } } return zero = true, false; } inline bool test(int k) { for (int i = B - 1; ~i; --i) if (k >> i & 1) { if (p[i]) k ^= p[i]; else return false; } return true; }
求子集与某数的异或最值
因为低位不会影响高位,直接从线性基的最高位开始贪心即可,时间复杂度 O(logV) 。
inline int querymax(int res = 0) { for (int i = B - 1; ~i; --i) if ((res ^ p[i]) > res) res ^= p[i]; return res; } inline int querymin(int res) { for (int i = B - 1; ~i; --i) if ((res ^ p[i]) < res) res ^= p[i]; return res; }
求数集异或最值
异或最大值直接调用 querymax(0)
即可。
异或最小值一般就是线性基里的最小元素,因为插入这个元素的时候我们总是尽量让它的高位全消掉才来插入这一位。
需要特判原数集存在子集异或和为 0 的情况,时间复杂度 O(logV) 。
inline int querymin() { if (zero) return 0; for (int i = 0; i < B; ++i) if (p[i]) return p[i]; return -1; }
查询异或k小值
首先考虑,如果每一位的选择互不影响,那么将 k 二进制分解后选择即可。
但线性基不一定满足这个性质,于是考虑重新构造一个线性基。尽量把原来的线性基只留下最高位的 1 ,剩下的位都用小的基底消掉,从而使得线性基中的数的异或值不会相互影响。
时间复杂度 O(log2V) 。
inline void rebuild() { for (int i = B - 1; ~i; --i) for (int j = i - 1; ~j; --j) if (p[i] >> j & 1) p[i] ^= p[j]; } inline int kth(int k) { rebuild(); vector<int> d; for (int i = 0; i < B; ++i) if (p[i]) d.emplace_back(p[i]); if (zero) --k; if (k >= (1 << d.size())) return -1; int res = 0; for (int i = d.size() - 1; ~i; --i) if (k >> i & 1) res ^= d[i]; return res; }
查询排名
不难理解。
inline int getrank(int k) { int rk = 0; for (int i = 0, now = 1; i < B; ++i) if (p[i]) { if (k >> i & 1) rk += now; now <<= 1; } return rk; }
求并
将 A 的每个元素插入 B 即可,时间复杂度 O(log2V) 。
inline friend LinearBasis operator | (LinearBasis a, LinearBasis b) { for (int i = 0; i < B; ++i) if (b.p[i]) a.insert(b.p[i]); return a; }
求交
引理:若 V1,V2 是线性空间,B1,B2 是它们的一组基。令 W=B2∩V1 ,若 B1∪(B2∖W) 线性无关,则 W 是 V1∩V2 的一组基。
证明:考虑对于任意 x∈V1∩V2 ,假设 x 不能被 W 表示,则 v 一定可以被 S∪T 表示,其中 S⊆W,T⊆B2∖W,T≠∅ ,那么此时 T∪B1 一定线性相关,矛盾。
因此对于求 A∩B ,若 Bi 能被 A1∼x∪B1∼i−1 表示,则把 ⨁xi=1Ai 或 ⨁i−1j=1Bj 加入交集的线性基中即可。
inline friend LinearBasis operator & (LinearBasis a, LinearBasis b) { LinearBasis d = a, all = a; // all 表示目前所有可用的低位基 a = LinearBasis(); for (int i = B - 1; ~i; --i) { if (!b.p[i]) continue; ull k = 0, v = b.p[i]; // k 是把 b[i] 消至 0 所用到的 a 的异或和 for (int j = i; ~j; --j) if (v >> j & 1) { if (all.p[j]) v ^= all.p[j], k ^= d.p[j]; else { all.p[j] = v, d.p[j] = k; break; } } if (!v) a.insert(k); } return a; }
应用
基本应用
有 n 堆石子,第 i 堆有 ai 个。第一回合双方可以任意拿走若干堆棋子,第二回合开始与传统 Nim 游戏一样,求先手必胜时第一回合拿走的最小石子数,或报告无解。
n≤100
若在第一回合拿走石子后,剩下的石子存在异或和为 0 的子集,那么对手保留这个子集则会造成先手必败的局面。
考虑贪心从大到小把数加入线性基,若插入失败则必须取走。
#include <bits/stdc++.h> typedef long long ll; using namespace std; const int N = 1e2 + 7, B = 31; struct LinearBasis { int p[B]; inline bool insert(int x) { for (int i = B - 1; ~i; --i) if (k >> i & 1) { if (p[i]) k ^= p[i]; else return p[i] = k, true; } return false; } } lb; int a[N]; int n; signed main() { scanf("%d", &n); for (int i = 1; i <= n; ++i) scanf("%d", a + i); sort(a + 1, a + 1 + n, greater<int>()); ll ans = 0; for (int i = 1; i <= n; ++i) if (!lb.insert(a[i])) ans += a[i]; printf("%lld", ans); return 0; }
维护 n 个初始为空的集合,m 次操作:
- 向某个集合插入一个数。
- 查询一段区间集合并的子集异或最大值。
n,m≤5×104
线段树维护线性基区间集合并即可,时间复杂度 O((n+m)lognlog2V) 。
#include <bits/stdc++.h> using namespace std; const int N = 5e4 + 7, B = 31; struct LinearBasis { int p[B]; inline void insert(int k) { for (int i = B - 1; ~i; --i) if (k >> i & 1) { if (p[i]) k ^= p[i]; else { p[i] = k; break; } } } inline int querymax() { int res = 0; for (int i = B - 1; ~i; --i) if ((res ^ p[i]) > res) res ^= p[i]; return res; } inline friend LinearBasis getcup(LinearBasis a, LinearBasis b) { for (int i = 0; i < B; ++i) if (b.p[i]) a.insert(b.p[i]); return a; } }; int n, m; template <class T = int> inline T read() { char c = getchar(); bool sign = (c == '-'); while (c < '0' || c > '9') c = getchar(), sign |= (c == '-'); T x = 0; while ('0' <= c && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar(); return sign ? (~x + 1) : x; } namespace SMT { LinearBasis lb[N << 2]; inline int ls(int x) { return x << 1; } inline int rs(int x) { return x << 1 | 1; } void update(int x, int nl, int nr, int pos, int k) { lb[x].insert(k); if (nl == nr) return; int mid = (nl + nr) >> 1; if (pos <= mid) update(ls(x), nl, mid, pos, k); else update(rs(x), mid + 1, nr, pos, k); } LinearBasis query(int x, int nl, int nr, int l, int r) { if (l <= nl && nr <= r) return lb[x]; int mid = (nl + nr) >> 1; if (r <= mid) return query(ls(x), nl, mid, l, r); else if (l > mid) return query(rs(x), mid + 1, nr, l, r); else return getcup(query(ls(x), nl, mid, l, r), query(rs(x), mid + 1, nr, l, r)); } } // namespace SMT signed main() { m = read(), n = read(); while (m--) { int op = read(); if (op == 1) { int x = read(), k = read(); SMT::update(1, 1, n, x, k); } else { int l = read(), r = read(); printf("%d\n", SMT::query(1, 1, n, l, r).querymax()); } } return 0; }
P11620 [Ynoi Easy Round 2025] TEST_34
维护数列 a1∼n ,m 次操作:
- 将区间 [l,r] 中所有数异或 k 。
- 求区间中所有子序列的异或和异或 v 的最大值。
n,m≤5×104
考虑引入差分数组 bi=⊕ij=1ai ,则区间修改可以转化为单点修改。
查询时注意到 al∼r 的线性基与 al∪bl+1∼r 的线性基等价,因此只要合并两个线性基查询即可。
线段树维护即可做到 O(mlognlog2V) 。
#include <bits/stdc++.h> using namespace std; const int N = 5e4 + 7, B = 31; struct LinearBasis { int p[B]; inline LinearBasis() { memset(p, 0, sizeof(p)); } inline void insert(int k) { for (int i = B - 1; ~i; --i) if (k >> i & 1) { if (p[i]) k ^= p[i]; else { p[i] = k; break; } } } inline int querymax(int res) { for (int i = B - 1; ~i; --i) if ((res ^ p[i]) > res) res ^= p[i]; return res; } inline friend LinearBasis operator | (LinearBasis a, LinearBasis b) { for (int i = 0; i < B; ++i) if (b.p[i]) a.insert(b.p[i]); return a; } }; int a[N]; int n, m; template <class T = int> inline T read() { char c = getchar(); bool sign = (c == '-'); while (c < '0' || c > '9') c = getchar(), sign |= (c == '-'); T x = 0; while ('0' <= c && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar(); return sign ? (~x + 1) : x; } namespace BIT { int c[N]; inline void update(int x, int k) { for (; x <= n; x += x & -x) c[x] ^= k; } inline int query(int x) { int res = 0; for (; x; x -= x & -x) res ^= c[x]; return res; } } // namespace BIT namespace SMT { LinearBasis lb[N << 2]; inline int ls(int x) { return x << 1; } inline int rs(int x) { return x << 1 | 1; } void update(int x, int nl, int nr, int pos, int k) { if (nl == nr) { lb[x] = LinearBasis(), lb[x].insert(k); return; } int mid = (nl + nr) >> 1; if (pos <= mid) update(ls(x), nl, mid, pos, k); else update(rs(x), mid + 1, nr, pos, k); lb[x] = lb[ls(x)] | lb[rs(x)]; } LinearBasis query(int x, int nl, int nr, int l, int r) { if (l <= nl && nr <= r) return lb[x]; int mid = (nl + nr) >> 1; if (r <= mid) return query(ls(x), nl, mid, l, r); else if (l > mid) return query(rs(x), mid + 1, nr, l, r); else return query(ls(x), nl, mid, l, r) | query(rs(x), mid + 1, nr, l, r); } } // namespace SMT signed main() { n = read(), m = read(); for (int i = 1; i <= n; ++i) { a[i] = read(); BIT::update(i, a[i]), BIT::update(i + 1, a[i]); SMT::update(1, 1, n, i, a[i] ^ a[i - 1]); } while (m--) { int op = read(), l = read(), r = read(), k = read(); if (op == 1) { BIT::update(l, k), SMT::update(1, 1, n, l, BIT::query(l) ^ BIT::query(l - 1)); if (r + 1 <= n) BIT::update(r + 1, k), SMT::update(1, 1, n, r + 1, BIT::query(r + 1) ^ BIT::query(r)); } else { if (l == r) { printf("%d\n", max(k, k ^ BIT::query(l))); continue; } LinearBasis res = SMT::query(1, 1, n, l + 1, r); res.insert(BIT::query(l)); printf("%d\n", res.querymax(k)); } } return 0; }
给定集合 S1∼n ,q 次询问,每次询问下标最小的不存在子集异或和为 k 的集合。
n≤105 ,q≤106
设 Ai 表示 S1∼i 线性基的交集,则可以二分找到最小的不能表示 k 的 Ai 即为答案,时间复杂度 O(nlog2V+qlognlog2V) ,无法通过。
注意到后面的线性基是前面线性基的子线性基,考虑按照基底的消失时间建立一个新的线性基,每次查询这个新线性基中表示 k 的所有基底中第一个消失的即可。
时间复杂度 O(nlog2V+qlogV) 。
#include <bits/stdc++.h> typedef unsigned long long ull; using namespace std; const int N = 1e5 + 7, B = 64; int n, q; struct LinearBasis { ull p[B]; int d[B]; inline LinearBasis() { memset(p, 0, sizeof(p)); } inline void insert(ull k, int id = 0) { for (int i = B - 1; ~i; --i) if (k >> i & 1) { if (p[i]) k ^= p[i]; else { p[i] = k, d[i] = id; break; } } } inline bool test(ull k) { for (int i = B - 1; ~i; --i) if (k >> i & 1) { if (p[i]) k ^= p[i]; else return false; } return !k; } inline int query(ull k) { int res = n + 1; for (int i = B - 1; ~i; --i) if (k >> i & 1) { if (p[i]) k ^= p[i], res = min(res, d[i] + 1); else return 1; } return res; } inline friend LinearBasis operator & (LinearBasis a, LinearBasis b) { LinearBasis d = a, all = a; // all 表示目前所有可用的低位基 a = LinearBasis(); for (int i = B - 1; ~i; --i) { if (!b.p[i]) continue; ull k = 0, v = b.p[i]; // k 是把 b[i] 消至 0 所用到的 a 的异或和 for (int j = i; ~j; --j) if (v >> j & 1) { if (all.p[j]) v ^= all.p[j], k ^= d.p[j]; else { all.p[j] = v, d.p[j] = k; break; } } if (!v) a.insert(k); } return a; } } lb[N]; template <class T = int> inline T read() { char c = getchar(); bool sign = (c == '-'); while (c < '0' || c > '9') c = getchar(), sign |= (c == '-'); T x = 0; while ('0' <= c && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar(); return sign ? (~x + 1) : x; } signed main() { n = read(), q = read(); for (int i = 1; i <= n; ++i) { int k = read(); while (k--) lb[i].insert(read<ull>()); if (i > 1) lb[i] = lb[i] & lb[i - 1]; } LinearBasis all; for (int i = n; i; --i) for (int j = B - 1; ~j; --j) if (lb[i].p[j]) all.insert(lb[i].p[j], i); while (q--) printf("%d\n", all.query(read<ull>())); return 0; }
前缀线性基
给定 a1∼n ,q 次询问从给出区间中选若干位置,最大化异或和。
n,q≤5×105
若询问区间都是前缀,那么只要预处理所有前缀的线性基即可。
一个朴素的想法是用线段树查询区间的线性基,时间复杂度 O(nlog2V+qlognlog2V) ,无法通过。
思考询问非前缀的难点,发现有的位置在 [1,r] 时是有值的,而在 [l,r] 时没有值。
考虑维护所有前缀的线性基,但是每个位置的值均保留出现下标最靠后的。这样查询最大值时只要在 [1,r] 的线性基中查询该位置下标 ≥l 的值的异或最大值即可。
时间复杂度 O((n+q)logV) 。
#include <bits/stdc++.h> using namespace std; const int N = 5e5 + 7, B = 21; struct LinearBasis { int p[B], d[B]; inline void insert(int k, int id) { for (int i = B - 1; ~i; --i) if (k >> i & 1) { if (!p[i]) { p[i] = k, d[i] = id; break; } else { if (id > d[i]) swap(p[i], k), swap(d[i], id); k ^= p[i]; } } } inline int querymax(int l) { int res = 0; for (int i = B - 1; ~i; --i) if (d[i] >= l && (res ^ p[i]) > res) res ^= p[i]; return res; } } lb[N]; int a[N]; int n, q; signed main() { scanf("%d", &n); for (int i = 1; i <= n; ++i) scanf("%d", a + i), lb[i] = lb[i - 1], lb[i].insert(a[i], i); scanf("%d", &q); while (q--) { int l, r; scanf("%d%d", &l, &r); printf("%d\n", lb[r].querymax(l)); } return 0; }
给出 n 个二元组 (xi,yi) ,需要选出若干元素满足任意一个子集的 x 异或和不为 0 ,求 ∑y 的最大值。
n≤103
线性基模板,插入时尽量留下大的 y 即可。
#include <bits/stdc++.h> typedef long long ll; using namespace std; const int B = 64; struct LinearBasis { ll p[B]; int w[B]; inline void insert(ll k, int id) { for (int i = B - 1; ~i; --i) if (k >> i & 1) { if (p[i]) { if (id > w[i]) swap(p[i], k), swap(w[i], id); k ^= p[i]; } else { p[i] = k, w[i] = id; break; } } } } lb; int n; signed main() { scanf("%d", &n); for (int i = 1; i <= n; ++i) { ll x; int y; scanf("%lld%d", &x, &y); lb.insert(x, y); } int ans = 0; for (int i = 0; i < B; ++i) if (lb.p[i]) ans += lb.w[i]; printf("%d", ans); return 0; }
给出一棵包含 n 个点的树,q 次询问 maxV⊆Path(x,y)⨁x∈Vax 。
n≤2×104,q≤2×105
先将路径拆为两条到 LCA 的链。
使用线性基维护答案,对于每个线性基,贪心的让高位的向量对应的点深度尽量大,查询时只要查询深度合法的点即可。
时间复杂度 O(nlog2V) 。
#include <bits/stdc++.h> typedef long long ll; using namespace std; const int N = 2e4 + 7, LOGN = 15, Q = 2e5 + 7, B = 63; struct LinearBasis { ll p[B]; int d[B]; inline LinearBasis() { memset(p, 0, sizeof(p)); memset(d, -1, sizeof(d)); } inline void insert(ll k, int id) { for (int i = B - 1; ~i; --i) if (k >> i & 1) { if (p[i]) { if (id > d[i]) swap(p[i], k), swap(d[i], id); k ^= p[i]; } else { p[i] = k, d[i] = id; break; } } } inline ll querymax() { ll res = 0; for (int i = B - 1; ~i; --i) if ((res ^ p[i]) > res) res ^= p[i]; return res; } } ans[Q]; struct Graph { vector<int> e[N]; inline void insert(int u, int v) { e[u].emplace_back(v); } } G; vector<pair<int, int> > qry[N]; ll a[N]; int fa[N][LOGN], dep[N]; int n, q; template <class T = int> inline T read() { char c = getchar(); bool sign = (c == '-'); while (c < '0' || c > '9') c = getchar(), sign |= (c == '-'); T x = 0; while ('0' <= c && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar(); return sign ? (~x + 1) : x; } void dfs1(int u, int f) { fa[u][0] = f, dep[u] = dep[f] + 1; for (int i = 1; i < LOGN; ++i) fa[u][i] = fa[fa[u][i - 1]][i - 1]; for (int v : G.e[u]) if (v != f) dfs1(v, u); } inline int LCA(int x, int y) { if (dep[x] < dep[y]) swap(x, y); for (int i = 0, h = dep[x] - dep[y]; h; ++i, h >>= 1) if (h & 1) x = fa[x][i]; if (x == y) return x; for (int i = LOGN - 1; ~i; --i) if (fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i]; return fa[x][0]; } inline void dfs2(int u, LinearBasis lb) { lb.insert(a[u], dep[u]); for (auto it : qry[u]) for (int i = B; ~i; --i) if (lb.d[i] >= it.first) ans[it.second].insert(lb.p[i], dep[u]); for (int v : G.e[u]) if (v != fa[u][0]) dfs2(v, lb); } signed main() { n = read(), q = read(); for (int i = 1; i <= n; ++i) a[i] = read<ll>(); for (int i = 1; i < n; ++i) { int u = read(), v = read(); G.insert(u, v), G.insert(v, u); } dfs1(1, 0); for (int i = 1; i <= q; ++i) { int x = read(), y = read(), lca = LCA(x, y); qry[x].emplace_back(dep[lca], i), qry[y].emplace_back(dep[lca], i); } dfs2(1, LinearBasis()); for (int i = 1; i <= q; ++i) printf("%lld\n", ans[i].querymax()); return 0; }
图上异或边权环
给定一张带权无向图,求一条 1 到 n 的路径(不一定是简单路径),最大化路径上边权异或和。
n≤5×104 ,m≤105
注意到对于一个环,可以选整个环的边权异或和或不选,因为走到环和走回来边权会抵消。
先找出所有的环,由于这里的边权是异或的,因此只要将 dfs 树上一条返祖边与若干树边组成的环丢进线性基即可,可以证明任意一个环的边权异或和可以由这些环线性组合得到。
最后查询与任意一条合法路径的权值与所有环的子集的异或最大值即可,时间复杂度 O(nlogV) 。
类似的题(最大改最小):CF845G Shortest Path Problem?
#include <bits/stdc++.h> typedef long long ll; using namespace std; const int N = 5e4 + 7, B = 64; struct Graph { vector<pair<int, ll> > e[N]; inline void insert(int u, int v, ll w) { e[u].emplace_back(v, w); } } G; struct LinearBasis { ll p[B]; inline void insert(ll x) { for (int i = B - 1; ~i; --i) if (x >> i & 1) { if (p[i]) x ^= p[i]; else { p[i] = x; break; } } } inline ll querymax(ll res) { for (int i = B - 1; ~i; --i) if ((res ^ p[i]) > res) res ^= p[i]; return res; } } lb; ll dis[N]; bool vis[N]; int n, m; template <class T = int> inline T read() { char c = getchar(); bool sign = (c == '-'); while (c < '0' || c > '9') c = getchar(), sign |= (c == '-'); T x = 0; while ('0' <= c && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar(); return sign ? (~x + 1) : x; } void dfs(int u, int f) { vis[u] = true; for (auto it : G.e[u]) { int v = it.first; ll w = it.second; if (v == f) continue; else if (vis[v]) lb.insert(dis[u] ^ w ^ dis[v]); else dis[v] = dis[u] ^ w, dfs(v, u); } } signed main() { n = read(), m = read(); for (int i = 1; i <= m; ++i) { int u = read(), v = read(); ll w = read<ll>(); G.insert(u, v, w), G.insert(v, u, w); } dfs(1, 0); printf("%lld", lb.querymax(dis[n])); return 0; }
CF724G Xor-matic Number of the Graph
给出一张无向带权图,三元组 (u,v,s) 合法当且仅当存在一条 u 到 v 且权值异或和为 s 的路径(不一定要简单路径),求所有合法三元组 s 的和 mod109+7 。
n≤105 ,m≤2×105
构建 dfs 树,则 u→v 存在一条边权异或和为 disu⊕disv 的路径。
先求出所有简单环的异或和丢到线性基里面,然后按位考虑贡献。
不妨设线性基大小为 siz 且当前位为 i,那么求出所有第 i 位为 0 和 1 的 disu 的个数,分别记为 z 和 o。这样就可以算出当前位异或起来为 0 和 1 的数对个数,即 cnt0=(z2)+(o2),cnt1=z×o 。
若线性基第 i 位有值,则线性基共异或出 2siz−1 个第 i 位为 0 或 1 的数,贡献为 (cnt0+cnt1)×2siz−1×2i 。否则线性基共能异或出 2siz 个第 i 位为 0 的数,贡献为 cnt1×2siz×2i 。
时间复杂度 O(nlogV)。
#include <bits/stdc++.h> typedef long long ll; using namespace std; const int Mod = 1e9 + 7, inv2 = (Mod + 1) / 2; const int N = 1e5 + 7, B = 64; struct Graph { vector<pair<int, ll> > e[N]; inline void insert(int u, int v, ll w) { e[u].emplace_back(v, w); } } G; struct LinearBasis { ll p[B]; int siz; inline void clear() { memset(p, 0, sizeof(p)), siz = 0; } inline void insert(ll k) { for (int i = B - 1; ~i; --i) if (k >> i & 1) { if (p[i]) k ^= p[i]; else { p[i] = k, ++siz; break; } } } } lb; vector<int> vec; ll dis[N]; int pw[N]; bool vis[N]; int n, m; template <class T = int> inline T read() { char c = getchar(); bool sign = (c == '-'); while (c < '0' || c > '9') c = getchar(), sign |= (c == '-'); T x = 0; while ('0' <= c && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar(); return sign ? (~x + 1) : x; } inline int add(int x, int y) { x += y; if (x >= Mod) x -= Mod; return x; } void dfs(int u, int f) { vis[u] = true, vec.emplace_back(u); for (auto it : G.e[u]) { int v = it.first; ll w = it.second; if (v == f) continue; else if (vis[v]) lb.insert(dis[u] ^ w ^ dis[v]); else dis[v] = dis[u] ^ w, dfs(v, u); } } signed main() { n = read(), m = read(); for (int i = 1; i <= m; ++i) { int u = read(), v = read(); ll w = read<ll>(); G.insert(u, v, w), G.insert(v, u, w); } pw[0] = 1; for (int i = 1; i <= n; ++i) pw[i] = 2ll * pw[i - 1] % Mod; int ans = 0; for (int i = 1; i <= n; ++i) { if (vis[i]) continue; lb.clear(), vec.clear(), dfs(i, 0); for (int j = 0; j < B; ++j) { bool flag = false; for (int k = 0; k < B; ++k) if (lb.p[k] >> j & 1) { flag = true; break; } int c[2] = {0, 0}; for (int it : vec) ++c[dis[it] >> j & 1]; int cnt0 = (1ll * c[0] * (c[0] - 1) / 2 + 1ll * c[1] * (c[1] - 1) / 2) % Mod, cnt1 = 1ll * c[0] * c[1] % Mod; if (flag) ans = add(ans, 1ll * add(cnt0, cnt1) * pw[lb.siz - 1] % Mod * pw[j] % Mod); else ans = add(ans, 1ll * cnt1 % Mod * pw[lb.siz] % Mod * pw[j] % Mod); } } printf("%d", ans); return 0; }
挖掘线性基性质
给定一棵树,点带点权,q 次操作:
- 将 x,y 间的简单路径上的所有点的点权异或上 z 。
- 询问是否存在 A,B⊆Path(x,y) 满足 A≠B 且 ⨁x∈Avalx=⨁x∈Bvalx 。
n≤105
修改直接用树剖处理,查询实际上就是判断 P(x,y) 是否有点无法插入线性基。
注意到线性基元素最多 O(logV) 个,即若 |Path(x,y)|>logV 则必定有点无法插入,否则暴力判断即可。
时间复杂度 O(nlognlog2V) 。
#include <bits/stdc++.h> using namespace std; const int N = 1e5 + 7, B = 31; struct Graph { vector<int> e[N]; inline void insert(int u, int v) { e[u].emplace_back(v); } } G; struct LinearBasis { int p[N]; inline LinearBasis() { memset(p, 0, sizeof(p)); } inline bool insert(int k) { for (int i = B - 1; ~i; --i) if (k >> i & 1) { if (p[i]) k ^= p[i]; else return p[i] = k, true; } return false; } }; int a[N], fa[N], dep[N], siz[N], son[N], top[N], dfn[N]; int n, m, dfstime; template <class T = int> inline T read() { char c = getchar(); bool sign = (c == '-'); while (c < '0' || c > '9') c = getchar(), sign |= (c == '-'); T x = 0; while ('0' <= c && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar(); return sign ? (~x + 1) : x; } void dfs1(int u, int f) { fa[u] = f, dep[u] = dep[f] + 1, siz[u] = 1; for (int v : G.e[u]) { if (v == f) continue; dfs1(v, u), siz[u] += siz[v]; if (siz[v] > siz[son[u]]) son[u] = v; } } void dfs2(int u, int topf) { top[u] = topf, dfn[u] = ++dfstime; if (son[u]) dfs2(son[u], topf); for (int v : G.e[u]) if (v != fa[u] && v != son[u]) dfs2(v, v); } inline int LCA(int x, int y) { while (top[x] != top[y]) { if (dep[top[x]] < dep[top[y]]) swap(x, y); x = fa[top[x]]; } return dep[x] < dep[y] ? x : y; } namespace BIT { int c[N]; inline void modify(int x, int k) { for (; x <= n; x += x & -x) c[x] ^= k; } inline void update(int l, int r, int k) { modify(l, k), modify(r + 1, k); } inline int query(int x) { int res = 0; for (; x; x -= x & -x) res ^= c[x]; return res; } } // namespace BIT inline void update(int x, int y, int z) { while (top[x] != top[y]) { if (dep[top[x]] < dep[top[y]]) swap(x, y); BIT::update(dfn[top[x]], dfn[x], z), x = fa[top[x]]; } if (dep[x] > dep[y]) swap(x, y); BIT::update(dfn[x], dfn[y], z); } inline bool query(int x, int y) { int lca = LCA(x, y); if (dep[x] + dep[y] - dep[lca] * 2 >= B) return true; LinearBasis lb; for (; x != lca; x = fa[x]) if (!lb.insert(BIT::query(dfn[x]))) return true; for (; y != lca; y = fa[y]) if (!lb.insert(BIT::query(dfn[y]))) return true; return !lb.insert(BIT::query(dfn[lca])); } signed main() { n = read(), m = read(); for (int i = 1; i <= n; ++i) a[i] = read(); for (int i = 1; i < n; ++i) { int u = read(), v = read(); G.insert(u, v), G.insert(v, u); } dfs1(1, 0), dfs2(1, 1); for (int i = 1; i <= n; ++i) BIT::update(dfn[i], dfn[i], a[i]); while (m--) { char str[7]; scanf("%s", str); if (str[0] == 'U') { int x = read(), y = read(), z = read(); update(x, y, z); } else { int x = read(), y = read(); puts(query(x, y) ? "YES" : "NO"); } } return 0; }
CF959F Mahmoud and Ehab and yet another xor task
给定 a1∼n ,q 次询问前 a1∼l 中有多少子集异或和为 x ,答案对 109+7 取模。
n,q≤105
考虑取出 a1∼l 的线性基,若 x 不能被表示则答案为 0 。否则考虑不在线性基内的元素的数,这些数都可以被线性基异或为 0 ,都可以选或不选,于是答案即为 2l−siz ,其中 siz 表示线性基大小。
时间复杂度 O((n+m)logV) 。
#include <bits/stdc++.h> using namespace std; const int Mod = 1e9 + 7; const int N = 1e5 + 7, B = 20; struct Linear { int p[B]; int siz; inline void insert(int k) { for (int i = B - 1; ~i; --i) if (k >> i & 1) { if (p[i]) k ^= p[i]; else { p[i] = k, ++siz; break; } } } inline bool query(int k) { for (int i = B - 1; ~i; --i) if (k >> i & 1) { if (p[i]) k ^= p[i]; else return false; } return true; } } lb[N]; int a[N], pw[N]; int n, m; signed main() { scanf("%d%d", &n, &m); pw[0] = 1; for (int i = 1; i <= n; ++i) pw[i] = 2ll * pw[i - 1] % Mod; for (int i = 1; i <= n; ++i) scanf("%d", a + i), lb[i] = lb[i - 1], lb[i].insert(a[i]); while (m--) { int x, k; scanf("%d%d", &x, &k); printf("%d\n", lb[x].query(k) ? pw[x - lb[x].siz] : 0); } return 0; }
给定 a1∼n ,求 m 在所有 2n 个子集异或和的排名。
n≤105
记 a1∼n 的线性基大小为 siz ,则有 2siz 个不同的数,每个数出现了 2n−siz 次。
因此答案即为 m 在线性基中的排名乘上 2n−siz 。
#include <bits/stdc++.h> using namespace std; const int Mod = 10086; const int N = 2e5 + 7, B = 31; struct LinearBasis { int p[B]; int siz; inline void insert(int k) { for (int i = B - 1; ~i; --i) if (k >> i & 1) { if (p[i]) k ^= p[i]; else { p[i] = k, ++siz; break; } } } inline int getrank(int k) { int rk = 0; for (int i = 0, now = 1; i < B; ++i) if (p[i]) { if (k >> i & 1) rk += now; now <<= 1; } return rk; } } lb; int a[N]; int n, m; signed main() { scanf("%d", &n); for (int i = 1; i <= n; ++i) scanf("%d", a + i), lb.insert(a[i]); scanf("%d", &m); int ans = lb.getrank(m); for (int i = 1; i <= n - lb.siz; ++i) ans = 2ll * ans % Mod; printf("%d", (ans + 1) % Mod); return 0; }
给出集合 S ,定义排列 p0∼2x−1 合法当且仅当排列中任意两个相邻的元素的异或值 ∈S 。求 x 最大的一个合法排列。
|S|,Si≤2×105
首先有 x≤log(maxS)+1 (否则一定有一对异或值有最高位,而 S 没有这个最高位),因此考虑枚举所有的 x 判断可行性。
猜测:一个 x 可行当且仅当 0∼2x−1 都可以表示为 S 的一个子集异或和,即 S 的 <2x 的数组成的线性基大小为 x 。
必要性:找到排列中 0 的位置,向两边扩展即可证明。
充分性:只要构造一个排列即可。考虑求出 0∼2x−1 对应线性基的哪个子集,把这些子集按照选取方案用二进制表示为 0∼2x−1 。因为线性基的每个位置存的都是一个子集的异或和,问题转化为把 0∼2x−1(所有子集)重新排列,使得任意相邻两个数,只有一位二进制为不同,构造格雷码即可。
#include <bits/stdc++.h> using namespace std; const int N = 4e5 + 7, B = 19; struct LinearBasis { int p[B], mark[B]; int siz; inline void insert(int k) { for (int i = B - 1, state = 0; ~i; --i) if (k >> i & 1) { if (p[i]) k ^= p[i], state ^= mark[i]; else { p[i] = k, mark[i] = state | (1 << i), ++siz; break; } } } inline int query(int k) { int res = 0; for (int i = B - 1; ~i; --i) if (k >> i & 1) k ^= p[i], res ^= mark[i]; return res; } } lb; int a[N], id[N]; int n; signed main() { scanf("%d", &n); for (int i = 1; i <= n; ++i) scanf("%d", a + i); sort(a + 1, a + 1 + n); int ans = 0; for (int i = 1, j = 0; i <= n; ++j) { while (a[i] < (1 << j) && i <= n) lb.insert(a[i++]); if (lb.siz == j) ans = j; } printf("%d\n", ans); for (int i = 0; i < (1 << ans); ++i) id[lb.query(i)] = i; for (int i = 0, cur = 0; i < (1 << ans); ++i) { printf("%d ", id[cur]); if (i & 1) cur ^= (cur & -cur) << 1; else cur ^= 1; } return 0; }
带删线性基
给出一张 n 个点 m 条边的无向连通图,边带边权 wi 。q 次操作,操作有:
- 在点 x,y 之间加入一条边权为 wi 的边。
- 将第 k 条新边权值改为 wi 。
- 删掉第 k 条新边,保证这条边之后不会再出现。
对于初始状态和每次操作后,从图中找到一条从 1 出发,并回到 1 的一条权值最大路径,路径权值定义为经过边边权的异或和。点边均可多次经过,边经过多次边权也会被计算多次。
n,m≤500 ,q,logW≤103 ,加边操作不不超过 550 次
将问题分为三个部分:
- 找到所有的环。
- 计算所有环的异或最大值。
- 对某个环的权值单点修改或删除某个环。
前两者是简单的,直接带权并查集配合线性基维护即可。
考虑将删除操作转化为将这个环的权值异或到 0 ,这样走到这个环就没有贡献,就没有删边操作。问题转化为维护一个支持单点异或的集合的线性基。
考虑维护每一个数插入时被异或上的基底集合 csti ,假设现在单点修改 x :
-
找到一个不在线性基里面的数 i 并满足 x∈csti ,如果找不到就贪心找最低的包含 x 的基底。
-
然后用选出的这个数消去其它所有包含 x 的基底,这样就消去了线性基中 x 的影响。
-
最后修改这个选出的基底,再插入线性基即可。
单次修改复杂度 O(n+logW) 。
记总共加入 x 条新边,则总时间复杂度为 O(nlogn+q(x+m+logW)logWω) 。
#include <bits/stdc++.h> using namespace std; const int N = 2e3 + 7; typedef bitset<N> bst; bst val[N], cir[N]; int id[N]; int n, m, q, tot; struct LinearBasis { bst val[N], cst[N]; // val : 基底值 | cst(consist) : 组成 int pos[N]; // 第 i 位基底对应值的下标 inline LinearBasis() { for (int i = 0; i < N; ++i) cst[i].set(i); } inline void update(const bst k, int id) { int cur = 0; for (int i = 1; i <= tot; ++i) if (val[i].none() && cst[i].test(id)) { cur = i; break; } if (!cur) { for (int i = 0; i < N; ++i) if (pos[i] && cst[pos[i]].test(id)) { cur = pos[i], pos[i] = 0; break; } } for (int i = 1; i <= tot; ++i) if (i != cur && cst[i].test(id)) val[i] ^= val[cur], cst[i] ^= cst[cur]; val[cur] ^= k; for (int i = N - 1; ~i; --i) if (val[cur].test(i)) { if (pos[i]) val[cur] ^= val[pos[i]], cst[cur] ^= cst[pos[i]]; else { pos[i] = cur; break; } } } inline bst query() { bst res; for (int i = N - 1; ~i; --i) if (!res.test(i) && pos[i]) res ^= val[pos[i]]; return res; } } lb; struct DSU { bst dis[N]; int fa[N], siz[N]; inline void clear(int n) { iota(fa + 1, fa + n + 1, 1), fill(siz + 1, siz + n + 1, 1); for (int i = 1; i <= n; ++i) dis[i].reset(); } inline int find(int x) { while (x != fa[x]) x = fa[x]; return x; } inline bst dist(int x) { bst res = dis[x]; while (x != fa[x]) x = fa[x], res ^= dis[x]; return res; } inline void merge(int x, int y, bst w, int k) { int fx = find(x), fy = find(y); if (fx == fy) { bst dis = dist(x) ^ dist(y) ^ w; cir[id[k] = ++tot] = dis, lb.update(dis, tot); } if (siz[fx] > siz[fy]) swap(fx, fy); dis[fx] = dist(x) ^ dist(y) ^ w; siz[fy] += siz[fx], fa[fx] = fy; } } dsu; template <class T = int> inline T read() { char c = getchar(); bool sign = (c == '-'); while (c < '0' || c > '9') c = getchar(), sign |= (c == '-'); T x = 0; while ('0' <= c && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar(); return sign ? (~x + 1) : x; } inline bst readbst() { char str[N]; scanf("%s", str); int len = strlen(str); reverse(str, str + len); bst x; for (int i = 0; i < len; ++i) x.set(i, str[i] & 15); return x; } inline void write(bst x) { bool lead = false; for (int i = N - 1; ~i; --i) { if (x.test(i)) putchar('1'), lead = true; else if (lead) putchar('0'); } if (!lead) putchar('0'); puts(""); } signed main() { n = read(), m = read(), q = read(); dsu.clear(n); for (int i = 1; i <= m; ++i) { int u = read(), v = read(); dsu.merge(u, v, readbst(), 0); } write(lb.query()); int ext = 0; while (q--) { char op[7]; scanf("%s", op); if (op[0] == 'A') { int u = read(), v = read(); bst w = readbst(); val[++ext] = w, dsu.merge(u, v, w, ext); } else if (op[1] == 'h') { int k = read(); bst w = readbst(); swap(w, val[k]), w ^= val[k]; lb.update(w, id[k]), cir[id[k]] ^= w; } else { int k = read(); lb.update(cir[id[k]], id[k]); } write(lb.query()); } return 0; }
本文作者:wshcl
本文链接:https://www.cnblogs.com/wshcl/p/18710542/LinearBasis
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步