线性基学习笔记
线性基具有的性质:
线性基插入和查询的写法
Menci Version
单次插入时间复杂度为 \(O(log^2 n)\)
单次查询时间复杂度为 \(O(log n)\)
const int MAXL = 60;
struct LinearBasis{
long long a[MAXL + 1];
LinearBasis(){std::fill(a, a + MAXL + 1, 0);}
void insert(long long t){
// 逆序枚举二进制位
for (int j = MAXL; j >= 0; j--){
// 如果 t 的第 j 位为 0,则跳过
if (!(t & (1ll << j))) continue;
// 如果 a[j] != 0,则用 a[j] 消去 t 的第 j 位上的 1
if (a[j]) t ^= a[j];
else{
// 找到可以插入 a[j] 的位置
// 用 a[0...j - 1] 消去 t 的第 [0, j) 位上的 1
// 如果某一个 a[k] = 0 也无须担心,因为这时候第 k 位不存在于线性基中,不需要保证 t 的第 k 位为 0
for (int k = 0; k < j; k++) if (t & (1ll << k)) t ^= a[k];
// 用 t 消去 a[j + 1...L] 的第 j 位上的 1
for (int k = j + 1; k <= MAXL; k++) if (a[k] & (1ll << j)) a[k] ^= t;
// 插入到 a[j] 的位置上
a[j] = t;
// 不要忘记,结束插入过程
return;
}
// 此时 t 的第 j 位为 0,继续寻找其最高位上的 1
}
// 如果没有插入到任何一个位置上,则表明 t 可以由 a 中若干个元素的异或和表示出,即 t 在 span(a) 中
}
long long queryMax(){
long long res = 0;
for (int i = 0; i <= MAXL; i++)
res ^= a[i];
return res;
}
};
Another Version
单次插入时间复杂度 \(O(log n)\)
单次查询时间复杂度 \(O(log n)\)
typedef unsigned long long ull;
const int MAXL = 63;
ull p[MAXL + 5];
inline void ins(ull x){
for(int i = MAXL; i + 1; -- i){
if((x >> i) & 1){
if(!p[i]){
p[i] = x;
return ;
}
x ^= p[i];
}
}
return ;
}
ull query(ull x) {
ull res = x;
for (int i = MAXL; i + 1; -- i)
if((res ^ p[i]) > res)
res ^= p[i];
return res;
}
序列上的线性基
引题
CF1100F Ivan and Burgers
题意: 给定一个序列\(\{ a_n \}\)和\(q\)次查询, 每次查询一个区间\([l, r]\), 求在这段区间内选取任意个数字的异或和最大是多少
Solution.
首先我们考虑用线段树来维护线性基,每次在父节点将两个区间的线性基暴力合并
复杂度为\(O(n log ^ 3 n)\)
但是在序列上我们可以换一种思路
考虑离线做法: 我们先把询问区间按照右端点排序, 然后构造序列上 \([1, r]\) 的线性基, 再令 \(pos_i\) 表示 \([l , r]\) 线性基第 \(i\) 位有值的最大的 \(l\),每一次按照普通线性基插入的方式向线性基\(base\)中插入\(a_i\)。处理答案时,如果\(base_i\)有值,且\(pos_i \geq l\),那么就代表这一位是在 \([l, r]\) 上的, 所以我们就可以选取了
Sample Code.
#include<cstdio>
#include<algorithm>
#define rep(i, a, b) for(register int i = (a); i <= (b); ++ i)
#define drep(i, a, b) for(register int i = (a); i >= (b); -- i)
template <typename T>
extern inline void SWAP(T &a, T &b){
T t = a; a = b, b = t;
}
const int MAXN = 500005;
const int MAXBIT = 20;
const int MEMBIT = MAXBIT + 5;
int bas[MEMBIT],pos[MEMBIT],n,rt,a[MAXN],q;
struct quest{
int lt,rt,id;
bool operator < (const quest &tmp) const{
return rt<tmp.rt;
}
}Q[MAXN];
void insert(int x,int id){
drep(i, MAXBIT, 0){
if(x & (1 << i)){
if(!bas[i]){
bas[i] = x,
pos[i] = id;
return ;
}
if(pos[i] < id)
SWAP(pos[i], id),
SWAP(x, bas[i]);
x ^= bas[i];
}
}
}
int work(int lt){
int ans = 0;
drep(i, MAXBIT, 0)
if(pos[i] >= lt && ans < (ans ^ bas[i]))
ans ^= bas[i];
return ans;
}
int ans[MAXN];
int main(){
scanf("%d",&n);
rep(i, 1, n)
scanf("%d",&a[i]);
scanf("%d",&q);
rep(i, 1, q)
scanf("%d%d",&Q[i].lt,&Q[i].rt),
Q[i].id = i;
std::sort(Q + 1, Q + 1 + q);
rep(i, 1, q){
while(rt < Q[i].rt){
++ rt;
insert(a[rt],rt);
}
ans[Q[i].id] = work(Q[i].lt);
}
rep(i, 1, q)
printf("%d\n",ans[i]);
return 0;
}
例题
P3292 [SCOI2016]幸运数字
给定一颗树,每个节点有点权
有\(q\)组询问, 每次询问两个点\(u, v\), 求在这两个点的简单路径上所有点权中任意选取点权异或和的最大值
Solution.
我们可以先对这棵树上的节点从节点到根节点上所有的点权维护在一个线性基里,每个节点维护一个线性基
然后我们就能够将这道题转化到序列上来做
Sample Code.
#include <cstdio>
#define ll long long
#define rep(i, a, b) for(register int i = (a); i <= (b); ++ i)
#define Rep(i, a, b) for(register int i = (a); i < (b); ++ i)
#define drep(i, a, b) for(register int i = (a); i >= (b); -- i)
#define fwd(i, u) for(register int i = head[(u)]; i; i = e[i].nxt)
ll rd() {
ll k = 0;
char c = getchar();
while (c > '9' || c < '0') c = getchar();
while (c >= '0' && c <= '9') k = k * 10 + c - '0', c = getchar();
return k;
}
const int N = 20001;
void upmax(ll& x, ll y) { if(x < y) x = y; }
int n, m, u, v, t, head[N], cnt, f[N], d[N], top[N], sz[N], son[N], lca;
ll a[N];
struct LB {
ll a[61]; int b[61];
void Insert(ll x, int v) {
drep(i, 60, 0){
if (!(x >> i))
continue;
if (!a[i]) {
a[i] = x, b[i] = v;
break;
}
x ^= a[i];
if (b[i] < v)
a[i] ^= x,
t = v, v = b[i], b[i] = t;
}
}
ll Query(int x) {
ll s = 0;
drep(i, 60, 0)
if (b[i] >= x)
upmax(s, s ^ a[i]);
return s;
}
} P[N], p;
struct E {
int v, nxt;
} e[N << 1];
void add(int u, int v) { e[++ cnt].v = v, e[cnt].nxt = head[u], head[u] = cnt; }
void dfs1(int u, int fa) {
d[u] = d[(f[u] = fa)] + 1, sz[u] = 1,
P[u] = P[fa], P[u].Insert(a[u], d[u]);
fwd(i, u){
int v = e[i].v;
if (v == fa)
continue;
dfs1(v, u), sz[u] += sz[v];
if (sz[son[u]] < sz[v])
son[u] = v;
}
}
void dfs2(int u, int t) {
top[u] = t;
if (!son[u])
return;
dfs2(son[u], t);
fwd(i, u){
int v = e[i].v;
if (v != f[u] && v != son[u])
dfs2(v, v);
}
}
int LCA(int x, int y) {
while (top[x] != top[y]) {
if (d[top[x]] > d[top[y]])
x = f[top[x]];
else
y = f[top[y]];
}
return d[x] < d[y] ? x : y;
}
int main() {
n = rd(), m = rd();
rep(i, 1, n)
a[i] = rd();
Rep(i, 1, n)
u = rd(), v = rd(),
add(u, v), add(v, u);
dfs1(1, 0), dfs2(1, 1);
while (m--) {
u = rd(), v = rd(),
lca = LCA(u, v), p = P[u];
drep(i, 60, 0)
if (P[v].b[i] >= d[lca] && P[v].a[i])
p.Insert(P[v].a[i], d[lca]);
printf("%lld\n", p.Query(d[lca]));
}
return 0;
}