2023大厂真题提交网址(含题解):
www.CodeFun2000.com(http://101.43.147.120/)
最近我们一直在将收集到的机试真题制作数据并搬运到自己的OJ上,供大家免费练习,体会真题难度。现在OJ已录入50+道2023年最新大厂真题,同时在不断的更新。同时,可以关注"塔子哥学算法"公众号获得每道题的题解。
前言:
对于大佬来讲这篇文章是无干货了。记录一下自己的理解。
从最开始去年集训的时候第一次听队长讲这个算法,懵懵懂懂,到后面第二次,第三次反复学习。直到今天的学习,我才真正的理解了线性基的原理。
学习好线性基的本质是:从线性代数的角度看问题,所以以下的文字中,我力求从线性代数的角度上描述问题.一旦碰到哪些概念混淆不清了,要么是我错了,要么是您需要马上捧起线性代数的书来好好再复习一下了。然后就是最好已经掌握高斯消元.
一.线性基是什么
线性基是 在系数域 F = { 0 , 1 } F=\{0,1\} F={0,1}下的一个向量空间 V V V的基.那么每个向量可以看作整数的二进制,而向量加法可以看作整数异或.
根据基的定义,我们知道线性基有如下几个特征:(这些性质都是基所具有的性质)
1.原序列里面的任意一个数都可以由线性基里面的一些数异或得到(表征整个向量空间)
2.线性基里面的任意一些数异或起来都不能得到
0
0
0. (线性无关)
3.线性基里面的数的个数唯一,并且在保持性质一的前提下,数的个数是最少的(向量空间的线性无关的极大集(即没有多余元素)).
二.如何求解线性基
这个问题就是在问我们:如何求解向量空间的基(先搞清楚怎么求这个)
那么做法就很显然了,我们利用类似高斯消元的方法将向量空间消解成上三角矩阵:
最开始是一个零矩阵,对于这 n n n个数 X X X.从第一行(最高位)开始,若 X X X的该位为1.则 若该位值为0,则将 X X X插入本行,否则异或完本行的值继续往下一行走,直到插入.
注意:如果理解不了这个过程,你就先将这 n n n个数展开成矩阵,然后用朴素的高斯消元的方法求一遍,你就知道为什么这么做是对的了。只是用这种方法比直接高斯消元快很多.这里我无法用简短的文字描述,就不写清楚了。
经过上面的过程我们容易发现以下几点性质:
1.1若存在不能插入到线性基里的数
a
i
a_i
ai.那么这个
a
i
a_i
ai能够被
[
1
,
i
−
1
]
[1,i-1]
[1,i−1]里的一个子集异或出来.反之不行.
1.2若存在不能插入到线性基里的数
a
i
a_i
ai,那么该序列能够异或出
0
0
0.
1.3根据抽屉原理:若
n
>
63
n > 63
n>63,那么该序列一定存在一个子集使得异或为0.
反证:若不存在,那代表每个数都插入成功了,那么就会有
n
>
63
n>63
n>63个基,但是实际上只有63个。所以假设不成立.
1.3的另一种表述:当
n
>
63
n>63
n>63时,该序列一定不是线性无关组.
2.线性基无法异或得到
0
0
0
三.线性基的作用:
1.求最大值:直接高位贪心求.正确性显然
2.求最小值:直接低位即可,但是注意,若原序列可以异或得到0,那么则需要考虑0.
3.求是否能够异或出某个数:利用线性基性质1.1,尝试将其插入到线性基。若插入的进去,则不能,反之可以。
4.求线性基能够异或出多少个本质不同的数出来:数对角线上 1 1 1的个数(线性基的大小)为 c n t cnt cnt。答案即为: 2 c n t 2^{cnt} 2cnt.因为一行选或不选会直接导致某一位的值是 0 0 0还是 1 1 1。
5:求第k小
1.重构线性基:
用类似高斯-约旦消元法的思想,将线性基消元成对角型.即每一列至多只会有一个1(其他的1都被消掉了).
做法:对于第 i i i行,遍历第 [ i − 1 → 0 ] [i-1 \rightarrow 0] [i−1→0].若 b [ i ] 与 ( 1 < < j ) = = 1 b[i] 与(1<<j) ==1 b[i]与(1<<j)==1,那么 b [ i ] = b [ i ] ⊕ b [ j ] b[i] =b[i]\oplus b[j] b[i]=b[i]⊕b[j].
这个时候每一个基的作用就只是独立的表征一个或一些位,任意两个基之间的异或 不再会在任何一位上产生 1 ⊕ 1 1 \oplus1 1⊕1的情况了。
例如:
[
1
1
0
0
1
1
0
0
1
]
\begin{bmatrix} 1& 1 &0\\ 0& 1 &1 \\ 0& 0& 1 \end{bmatrix}
100110011
重构之后:
[ 1 0 0 0 1 0 0 0 1 ] \begin{bmatrix} 1& 0 &0\\ 0& 1 &0 \\ 0& 0& 1 \end{bmatrix} 100010001
重构之后线性基的性质不变,而且还是对应的原来序列的线性基。
这个时候显然我们只需要将 k k k拆成二进制。然后如果第 i i i位是1,那么就将答案异或上第 i i i个基.(注意不是直接异或 b [ i − 1 ] b[i-1] b[i−1]).
正确性就很显然了,因为只要它们的 1 1 1之间一一对应,那么就是等价的。(基的选或不选的性质完美对应了 k k k的二进制形式)。大家意会一下(
基本模板:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
const int maxn = 1e5 + 5;
const int mod = 1e9 + 7;
ll b[66];
int n , cnt;
int up = 63;
void Insert (ll x)
{
for (int i = up ; i >= 0 ; i--){
if ((x & (1ll << i)) == 0) continue;
if (b[i]) x ^= b[i];
else {
b[i] = x;
break;
}
}
}
ll getMax ()
{
ll res = 0;
for (int i = up ; i >= 0 ; i--) res = max (res , res ^ b[i]);
return res;
}
// 消元重构
void rebuild()
{
for (int i = up ; i >= 0 ; i--){
if (!b[i]) continue;
for (int j = i - 1 ; j >= 0 ; j--)
if (b[i] & (1 << j))
b[i] ^= b[j];
}
}
ll ask (ll k)
{
if(k==1&&cnt<n)return 0;//特判一下,假如k=1,并且原来的序列可以异或出0,就要返回0,tot表示线性基中的元素个数,n表示序列长度
if(cnt<n)k--;//类似上面,去掉0的情况,因为线性基中只能异或出不为0的解
if (k >= (1ll << cnt)) return -1;
ll ans = 0;
for (int i = 0 ; i <= up ; i++){
if (b[i]){
if (k & 1) ans ^= b[i];
k >>= 1;
}
}
return ans;
}
int main()
{
ios::sync_with_stdio(false);
int t; cin >> t;
int tt = 0;
while (t--){
memset(b , 0 , sizeof b);
cnt = 0;
cin >> n;
for (int i = 1 ; i <= n ; i++){
ll x; cin >> x;
Insert(x);
}
for (int i = 0 ; i <= up ; i++) cnt += (b[i] != 0);
rebuild();
int q; cin >> q;
cout << "Case #" << (++tt) << ":" << endl;
for (int i = 1 ; i <= q ; i++){
ll k; cin >> k;
cout << ask (k) << endl;
}
}
return 0;
}
进阶骚操作:
6.线段树维护线性基并
很简单,将左儿子的线性基依次插入到右儿子的线性基中即可.
7.线段树维护线性基的交
https://blog.csdn.net/TDD_Master/article/details/97965051?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.control
8.线性基的删除操作
参考博客:
https://ksmeow.moe/linear_basis/
https://blog.csdn.net/a_forever_dream/article/details/83654397