1. 程式人生 > >BZOJ4568 SCOI2016 幸運數字 倍增的思想維護線性基(線性基詳解)

BZOJ4568 SCOI2016 幸運數字 倍增的思想維護線性基(線性基詳解)

題目大意

給你一顆N個節點的樹,每個節點都有一個權值Ai,現在有M組詢問,每組詢問有3個數u,v,,要求你輸出在樹上節點u到節點v的路徑上,每個節點的權值可以選或不選,求選出的點的權值的異或最大值。

N2104
M2105
Ai260

解題思路

看到這種區間異或最大值的題,那麼我們就要聯想到線性基,是一個只用維護Log(maxAi)個值就可以表示出區間所有的值的演算法,並且合併是Log(maxAi)2
那麼如果會線性基後,就可以先預處理出陣列F[i][j]表示從i這個節點到以i向上2j個節點的這段區間的線性基,然後對於每個詢問,有倍增的思想來合併預處理好的線性基就可以了。

那麼現在的問題就是什麼是線性基?

線性基

主要思想

線性基是一個主要解決有關異或方面的問題。它的主要思想就是用盡可能少的數表示出集合中的所有數。根據定義我們就可以得出線性基的幾個性質。
1. 對於集合的每一個數都可以有線性基中的是異或而來。
2. 線性基中的每個數都不可以有線性基中的其他數異或而來。

具體操作

實際上對於每組線性基只需要在二進位制上的每一位存一個數就可以了。這個很好理解,這種思想跟高斯消元很像,因為每一個第i位為1的數都可以與集合中其他第i為1的數異或,操作一次後,就只剩下當前數的這一位為1,所以說一個線性基只用存Log(maxAi)個數。

合併兩個線性基

合併兩個線性基時只需把其中一個線性基的數暴力加到另一箇中就可以了。加入時的操作也跟高斯消元類似,我們首先需要判斷的就是當前這個數能否被另一個線性基中的數表示。加入當前待加入的這個數的第i位為1,那麼我們就要判斷另一個線性基中的第i位有沒有存下數字。如果有那麼這一位就可以被消去,如果沒有那麼這個數就不能被另一個線性基表示,所以在第i位加入這個數。所以合併兩個線性基的複雜度是O(Log(maxAi)2)的。

程式

//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(); }