BZOJ4568 SCOI2016 幸運數字 倍增的思想維護線性基(線性基詳解)
題目大意
給你一顆
解題思路
看到這種區間異或最大值的題,那麼我們就要聯想到線性基,是一個只用維護
那麼如果會線性基後,就可以先預處理出陣列
那麼現在的問題就是什麼是線性基?
線性基
主要思想
線性基是一個主要解決有關異或方面的問題。它的主要思想就是用盡可能少的數表示出集合中的所有數。根據定義我們就可以得出線性基的幾個性質。
1. 對於集合的每一個數都可以有線性基中的是異或而來。
2. 線性基中的每個數都不可以有線性基中的其他數異或而來。
具體操作
實際上對於每組線性基只需要在二進位制上的每一位存一個數就可以了。這個很好理解,這種思想跟高斯消元很像,因為每一個第i位為1的數都可以與集合中其他第i為1的數異或,操作一次後,就只剩下當前數的這一位為1,所以說一個線性基只用存
合併兩個線性基
合併兩個線性基時只需把其中一個線性基的數暴力加到另一箇中就可以了。加入時的操作也跟高斯消元類似,我們首先需要判斷的就是當前這個數能否被另一個線性基中的數表示。加入當前待加入的這個數的第i位為1,那麼我們就要判斷另一個線性基中的第i位有沒有存下數字。如果有那麼這一位就可以被消去,如果沒有那麼這個數就不能被另一個線性基表示,所以在第i位加入這個數。所以合併兩個線性基的複雜度是
程式
//YxuanwKeith
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long LL;
const int MAXN = 2e4 + 5;
LL F[MAXN][16][65], Ans[65];
int N, M, Deep[MAXN], Fa[MAXN][16];
int tot, Last[MAXN], Next[MAXN * 2], Go[MAXN * 2];
void Link(int u, int v) {
Next[++ tot] = Last[u], Last[u] = tot, Go[tot] = v;
}
void Merge(LL *F, LL Now) {
for (int i = 60; i + 1; i --) {
if ((Now >> i) & 1ll) {
if (!F[i]) {
F[i] = Now;
return;
} else Now ^= F[i];
}
}
}
void MergeAll(LL *F, LL *G) {
for (int i = 60; i + 1; i --)
if (G[i]) Merge(F, G[i]);
}
void Dfs(int Now, int Pre) {
Deep[Now] = Deep[Pre] + 1;
Fa[Now][0] = Pre;
for (int p = Last[Now]; p; p = Next[p])
if (Go[p] != Pre) Dfs(Go[p], Now);
}
void Prepare() {
for (int j = 1; j <= 15; j ++)
for (int i = 1; i <= N; i ++) {
Fa[i][j] = Fa[Fa[i][j - 1]][j - 1];
memcpy(F[i][j], F[i][j - 1], sizeof F[i][j - 1]);
MergeAll(F[i][j], F[Fa[i][j - 1]][j - 1]);
}
}
void Lca(int x, int y) {
if (Deep[x] < Deep[y]) swap(x, y);
for (int i = 15; i + 1; i --)
if (Deep[Fa[x][i]] >= Deep[y]) {
MergeAll(Ans, F[x][i]);
x = Fa[x][i];
}
if (x == y) {
MergeAll(Ans, F[x][0]);
return;
}
for (int i = 15; i + 1; i --)
if (Fa[x][i] != Fa[y][i]) {
MergeAll(Ans, F[x][i]), MergeAll(Ans, F[y][i]);
x = Fa[x][i], y = Fa[y][i];
}
MergeAll(Ans, F[x][0]), MergeAll(Ans, F[y][0]);
MergeAll(Ans, F[Fa[x][0]][0]);
}
void Solve() {
for (int i = 1; i <= M; i ++) {
memset(Ans, 0, sizeof Ans);
int u, v;
scanf("%d%d", &u, &v);
Lca(u, v);
LL Sum = 0;
for (int j = 60; j + 1; j --) Sum = max(Sum, Sum ^ Ans[j]);
printf("%lld\n", Sum);
}
}
int main() {
scanf("%d%d", &N, &M);
for (int i = 1; i <= N; i ++) {
LL Now;
scanf("%lld", &Now);
Merge(F[i][0], Now);
}
for (int i = 1; i < N; i ++) {
int u, v;
scanf("%d%d", &u, &v);
Link(u, v), Link(v, u);
}
Dfs(1, 0);
Prepare();
Solve();
}