原文連接:https://oierbbs.fun/blog/399/3html
做者: @"Suzt_ilymtics"#38 node
推薦時間:2021年8月13日ios
更好的閱讀體驗?git
線性基是向量空間的一組基,一般能夠解決有關異或的一些題目。-Oi-wiki
線性基就是一個有着特殊性質的集合,在處理某些狀況下的異或問題有着意想不到的效果。算法
假設咱們用 p
數組來存線性基。segmentfault
線性基能夠由給出的一組元素相互異或而來。數組
而 p_i
中的元素表示該元素二進制下 1
的最高位是第 i
位。ui
p
數組定義比較顯然。)0
的子集。(每一個元素的最高位不一樣,異或起來最高位必定不會爲 0
。)通常狀況下都是在二進制下進行。url
假設插入一個元素爲 x
。spa
1
,設該位爲 i
。p_i
是否有值,若是沒有把 x
存到 p_i
中,不然將 x
與 p_i
異或而後重複上面的操做。(將 x
與 p_i
異或後 x
的最高位上的 1
就沒了,至於變成了啥也無所謂了)把全部元素都插入後,咱們就獲得了這組元素的線性基。
這樣構造的線性基知足它該有的全部性質,p_i
數組也符合定義。
Code:
void Insert(int k) { for(int i = Max; i >= 0; --i) { // Max 表示二進制最高位 if(!(k & (1ll << i))) continue; if(!p[i]) { p[i] = k; return ; } k ^= p[i]; } }
線性基的插入和下面的幾個操做複雜度都是 O(\log n)
的。
給你一堆元素,挑幾個異或起來,是他們的值最大,輸出最大值。
咱們先用這組元素構造出線性基。而後貪心的進行選擇,選高位的確定比低位的要更優。
因此咱們從高位向低位遍歷,若是異或上 p_i
更優就異或。最後的結果就是要求的最大值。
爲何能直接用?線性基的元素都是經過已知的元素異或而來,確定是正確的。
Code:
int Query() { int res = 0; for(int i = Max; i >= 0; --i) res = max(res, res^p[i]); return res;
把這個數扔到線性基裏跑一邊就行。
每次挑這個數的最高位進行異或,都能把最高位消掉。
若是最後這個數爲 0
,說明該數能夠被表示出。
把一個線性基的每一個元素插入另一個線性基便可。
先找到最大值,而後從低位向高位枚舉,而後找到二者都爲一的異或上, 而後退出便可。
以前咱們只是把線性基簡單化了,線性基最初是應用到向量裏的。
這道題思路和[P4570 [BJWC2011]元素](https://www.luogu.com.cn/prob...挺像的。
只不過這道題把整數換成了向量。向量中的每一個元素就至關於原來的每一個二進制位。
咱們能夠把 n
個裝備看做 n
個 m
維的向量。
如 k = ( a_1, a_2, a_3, ... , a_m )
大致思路是同樣的,結合高斯消元來構造。
emm看代碼會更直觀一點,感受我口胡的並非很清楚。
/* Work by: Suzt_ilymics Knowledge: ?? Time: O(??) */ #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<queue> #include<string> #define LL long long #define orz cout<<"lkp AK IOI!"<<endl using namespace std; const int MAXN = 1e5+5; const int INF = 1e9+7; const int mod = 1e9+7; const double lim = 1e-5; // lim 防止因精度損失而引起錯誤 int n, m, cnt = 0; int sum = 0; struct Vector { // 這裏把全部向量存到告終構體裏方便使用 int cost; double a[520]; // a 數組存向量的每一個元素的值 Vector () { for(int i = 1; i <= m; ++i) a[i] = 0; } bool operator < (const Vector &b) const { return cost < b.cost; } Vector operator + (const Vector b) { Vector res; for(int i = 1; i <= m; ++i) res.a[i] = a[i] + b.a[i]; return res; } Vector operator - (const Vector b) { Vector res; for(int i = 1; i <= m; ++i) res.a[i] = a[i] - b.a[i]; return res; } Vector operator * (const double b) { Vector res; for(int i = 1; i <= m; ++i) res.a[i] = a[i] * b; return res; } }ep[520]; // 存讀入的每一個向量 int read(){ int s = 0, f = 0; char ch = getchar(); while(!isdigit(ch)) f |= (ch == '-'), ch = getchar(); while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar(); return f ? -s : s; } namespace Lb { Vector p[520]; bool Insert(Vector k) { for(int i = m; i >= 1; --i) { // 從左向右依次遍歷每一個向量中的元素,這裏的遍歷順序已再也不重要 if(abs(k.a[i]) < lim) continue; // 若是插入向量的當前元素無值就跳過 if(abs(p[i].a[i]) < lim) { p[i] = k; return true; } // 若是線性基中「最高位」向量中沒有值,就插入。 double t = k.a[i] / p[i].a[i]; // 算出係數 k = k - p[i] * t; // 進行消元 } return false; } } int main() { n = read(), m = read(); for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) scanf("%lf", &ep[i].a[j]); // 讀入 n 個 向量 for(int i = 1; i <= n; ++i) ep[i].cost = read(); // 讀入每一個向量的花費,類比元素那道題 sort(ep + 1, ep + n + 1); // 按照花費從小到大排序貪心 for(int i = 1; i <= n; ++i) { if(Lb::Insert(ep[i])) { // 判斷可否插入 ++ cnt; // 計數 sum += ep[i].cost; // 記錄花費 } } printf("%d %d", cnt, sum); return 0; }
板子題,求最大值。
/* Work by: Suzt_ilymics Problem: 不知名屑題 Knowledge: 垃圾算法 Time: O(能過) */ #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<queue> #define int long long #define orz cout<<"lkp AK IOI!"<<endl using namespace std; const int MAXN = 1e5+5; const int INF = 1e9+7; const int mod = 1e9+7; const int bit = 50; int n, p[55]; int read(){ int s = 0, f = 0; char ch = getchar(); while(!isdigit(ch)) f |= (ch == '-'), ch = getchar(); while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar(); return f ? -s : s; } void Insert(int k) { for(int i = bit; i >= 0; --i) { if(!(k & (1ll << i))) continue; if(!p[i]) { p[i] = k; return ; } k ^= p[i]; } } int Maxxor() { int res = 0; for(int i = bit; i >= 0; --i) res = max(res, (res ^ p[i])); return res; } signed main() { n = read(); while(n--) Insert(read()); printf("%lld", Maxxor()); return 0; }
搞出線性基,統計線性基中有幾個元素,假設有 x
個,輸出 2^ x \bmod 2008
便可。
由於假設咱們有 p_i
那麼選與不選必定會決定第 i
位的是否爲 1
,那麼有幾個 p_i
咱們就能決定幾位,答案也就是 2^ x
。
/* Work by: Suzt_ilymics Problem: 不知名屑題 Knowledge: 垃圾算法 Time: O(能過) */ #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<queue> #define int long long #define orz cout<<"lkp AK IOI!"<<endl using namespace std; const int MAXN = 1e5+5; const int INF = 1e9+7; const int mod = 2008; const int Max = 50; char s[60]; int n, m, cnt = 0; int p[60]; int read(){ int s = 0, f = 0; char ch = getchar(); while(!isdigit(ch)) f |= (ch == '-'), ch = getchar(); while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar(); return f ? -s : s; } void Insert(int k) { for(int i = Max; i >= 0; --i) { if(!(k & (1ll << i))) continue; if(!p[i]) { p[i] = k; return ;} k ^= p[i]; } } signed main() { n = read(), m = read(); for(int i = 1; i <= m; ++i) { scanf("%s", s + 1); int x = 0; for(int j = 1; j <= n; ++j) if(s[j] == 'O') x |= (1ll << (j - 1)); Insert(x); } for(int i = Max; i >= 0; --i) if(p[i]) cnt++; printf("%lld\n", (1ll << cnt) % mod); return 0; }
首先咱們知道
若是a \oplus b = c
,那麼a \oplus c = b, b \oplus c = a
。
由前面推後面能夠看作兩邊同時異或 a
或者 b
。
假設 b_1 \oplus b_2 \oplus b_3 ... \oplus b_i = x
,x
比任何一個值都要大。
那麼 x \oplus b_ 1 \oplus b_ 2 \oplus b_ 3 ... \oplus b_ {i-1} = b_ i
。
爲了得到更大價值,咱們能夠按照價值從大到小排序,而後貪心的選擇。
每次向線性基插入它的 number
,若是能夠插入就選擇它。
/* Work by: Suzt_ilymics Problem: 不知名屑題 Knowledge: 垃圾算法 Time: O(能過) */ #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<queue> #define LL long long #define orz cout<<"lkp AK IOI!"<<endl using namespace std; const int MAXN = 1e5+5; const int INF = 1e9+7; const int mod = 1e9+7; struct node { LL k, val; bool operator < (const node &b) const { return val > b.val; } }a[MAXN]; LL n, ans = 0; LL read(){ LL s = 0, f = 0; char ch = getchar(); while(!isdigit(ch)) f |= (ch == '-'), ch = getchar(); while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar(); return f ? -s : s; } namespace Lb { const LL Max = 60; LL p[61]; bool Insert(LL k) { for(LL i = Max; i >= 0; --i) { if(!(k & (1ll << i))) continue; if(!p[i]) { p[i] = k; return true; } k ^= p[i]; } return false; } } int main() { n = read(); for(int i = 1; i <= n; ++i) a[i].k = read(), a[i].val = read(); sort(a + 1, a + n + 1); for(int i = 1; i <= n; ++i) if(Lb::Insert(a[i].k)) ans += a[i].val; printf("%lld", ans); return 0; }
乍一看沒有思路。一開始覺得能夠像上面那個題同樣貪心的選擇。
由於能夠重複通過,因此路徑就太多了。
假設咱們走到了 n
號點,咱們此時的異或和爲 dis_n
。
咱們想要知道有沒有更優的選擇,咱們假設往回走某條咱們沒有通過的路徑,若是咱們不能繞回來,也就是說沒有環,那麼在返回的途中還會再走一遍。衆所周知 x \oplus x = 0
。因此就至關於咱們啥也沒幹。
若是在走的途中繞了一個環,咱們從環的起點出發又回到了環的起點,環上的路徑都只被走了一次,說明什麼?咱們與環上的全部值異或了。
經過上面討論不難發現,通常路徑上的值不能選,環上的值可選可不選,那麼咱們把一個環當作一個元素構建線性基,挑幾個能讓咱們的 dis_n
變得更優的與之異或。
dis_n
怎麼求?隨便找一條路徑。
若是有更優的路徑,那麼它必定會與這條路徑造成一個環,異或這個環,就會變成選擇另外一條路徑。
/* Work by: Suzt_ilymics Problem: 不知名屑題 Knowledge: 垃圾算法 Time: O(能過) */ #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<queue> #define LL long long #define int long long #define orz cout<<"lkp AK IOI!"<<endl using namespace std; const int MAXN = 1e5+5; const int INF = 1e9+7; const int mod = 1e9+7; const int Max = 61; struct edge { int to, w, nxt; }e[MAXN << 1]; int head[MAXN], num_edge = 1; int n, m; int p[66], dis[MAXN]; bool vis[MAXN]; int read(){ int s = 0, f = 0; char ch = getchar(); while(!isdigit(ch)) f |= (ch == '-'), ch = getchar(); while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar(); return f ? -s : s; } void add_edge(int from, int to, int w) { e[++num_edge] = (edge){to, w, head[from]}, head[from] = num_edge; } void Insert(int k) { for(int i = Max; i >= 0; --i) { if(!(k & (1ll << i))) continue; if(!p[i]) { p[i] = k; return ; } k ^= p[i]; } } int Query(int res) { for(int i = Max; i >= 0; --i) res = max(res, res ^ p[i]); return res; } void dfs(int u, int sum) { dis[u] = sum, vis[u] = true; for(int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if(!vis[v]) dfs(v, dis[u] ^ e[i].w); else Insert(dis[u] ^ dis[v] ^ e[i].w); } } signed main() { n = read(), m = read(); for(int i = 1, u, v, w; i <= m; ++i) { u = read(), v = read(), w = read(); add_edge(u, v, w), add_edge(v, u, w); } dfs(1, 0); printf("%lld\n", Query(dis[n])); return 0; }
把每一個點看作一個線性基而後樹剖便可。
詢問時把每段區間的線性基合併求最大值便可。
總複雜度 O(n \log ^ 4n)
。
代碼寫的比較醜,須要吸氧才能過。
/* Work by: Suzt_ilymics Problem: 不知名屑題 Knowledge: 垃圾算法 Time: O(能過) */ #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<queue> #define LL long long #define orz cout<<"lkp AK IOI!"<<endl using namespace std; const int MAXN = 2e4+5; const int INF = 1e9+7; const int mod = 1e9+7; struct edge { int to, nxt; }e[MAXN << 1]; int head[MAXN], num_edge = 1; int n, Q, cnt = 0; LL a[MAXN]; int dep[MAXN], fath[MAXN], siz[MAXN], son[MAXN], top[MAXN], dfn[MAXN], pre[MAXN]; LL read(){ LL s = 0, f = 0; char ch = getchar(); while(!isdigit(ch)) f |= (ch == '-'), ch = getchar(); while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar(); return f ? -s : s; } void add_edge(int from, int to) { e[++num_edge] = (edge){to, head[from]}, head[from] = num_edge; } namespace Seg { #define lson i << 1 #define rson i << 1 | 1 struct Tree { LL p[66]; Tree () { memset(p, false, sizeof p); } void Insert(LL k) { for(int i = 60; i >= 0; --i) { if(!(k & (1ll << i))) continue; if(!p[i]) { p[i] = k; return ; } k ^= p[i]; } } LL Query() { LL res = 0; for(int i = 60; i >= 0; --i) res = max(res, res ^ p[i]); return res; } }tree[MAXN << 2]; void Push_up(int i) { tree[i] = tree[lson]; for(int j = 60; j >= 0; --j) if(tree[rson].p[j]) tree[i].Insert(tree[rson].p[j]); } void hb(Tree *x, Tree y) { for(int i = 60; i >= 0; --i) if(y.p[i]) x->Insert(y.p[i]); } void Build(int i, int l, int r) { if(l == r) { tree[i].Insert(a[pre[l]]); return ; } int mid = (l + r) >> 1; Build(lson, l, mid), Build(rson, mid + 1, r); Push_up(i); } Tree Query(int i, int l, int r, int L, int R) { if(L <= l && r <= R) return tree[i]; Tree ans; int mid = (l + r) >> 1; if(mid >= L) ans = Query(lson, l, mid, L, R); if(mid < R) hb(&ans, Query(rson, mid + 1, r, L, R)); return ans; } } namespace Cut { void dfs(int u, int fa) { dep[u] = dep[fa] + 1, fath[u] = fa, siz[u] = 1; for(int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if(v == fa) continue; dfs(v, u); siz[u] += siz[v]; if(siz[son[u]] < siz[v]) son[u] = v; } } void dfs2(int u, int tp) { top[u] = tp, dfn[u] = ++ cnt, pre[cnt] = u; if(son[u]) dfs2(son[u], tp); for(int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if(v == fath[u] || v == son[u]) continue; dfs2(v, v); } } Seg::Tree Query(int x, int y) { Seg::Tree ans; while(top[x] != top[y]) { if(dep[top[x]] < dep[top[y]]) swap(x, y); Seg::hb(&ans, Seg::Query(1, 1, n, dfn[top[x]], dfn[x])); x = fath[top[x]]; } if(dep[x] > dep[y]) swap(x, y); Seg::hb(&ans, Seg::Query(1, 1, n, dfn[x], dfn[y])); return ans; } } signed main() { n = read(), Q = read(); for(int i = 1; i <= n; ++i) a[i] = read(); for(int i = 1, u, v; i < n; ++i) u = read(), v = read(), add_edge(u, v), add_edge(v, u); Cut::dfs(1, 0), Cut::dfs2(1, 1), Seg::Build(1, 1, n); for(int i = 1, u, v; i <= Q; ++i) { u = read(), v = read(); Seg::Tree ans = Cut::Query(u, v); printf("%lld\n", ans.Query()); } return 0; }