『线性空间 整数线性基和异或线性基』
<更新提示>
<第一次更新>
<正文>
线性空间#
定义#
线性空间是一个关于一下两个运算封闭的向量集合:
1.向量加法a+b,其中a,b为向量
2.标量乘法k∗a,其中a为向量,k为常数
基础概念#
1.给定若干个向量a1,a2,...,an,若向量b能够通过a1,a2,...,an经过向量加法和标量乘法得到,则称向量b能够通过a1,a2,...,an表出。
2.对于向量a1,a2,...,an能够表出的所有向量所构成了一个线性空间,称a1,a2,...,an为这个线性空间的生成子集。
3.在一个线性空间中选取若干个向量,若一个向量能够被其他向量表出,则称这些向量线性相关,反之,称这些向量线性无关。
4.线性无关的生成子集成为线性空间的基底,简称基。
4′.另外地,线性空间的极大线性无关子集也称为线性空间的基底,简称基。
5.一个线性空间的所有基包含的向量个数都相等,这个个数称为线性空间的维数。
高斯消元和线性基#
对于一个n∗m的矩阵,我们可以把它看做n个长度为m的向量,即它的维数为m。那么如果将这个矩阵看做系数矩阵进行高斯消元,则消元后得到的矩阵中所有非0行代表的向量线性无关。
因为初等行变换就是对每行的向量之间进行向量加法和标量乘法,所以高斯消元的操作并不改变n个向量所能表出的线性空间,则消元后的矩阵的所有非0行所代表的向量就是原线性空间的一个基。
综上所述,我们可以利用高斯消元算法对若干个向量求解其构成线性空间的一个基。
装备购买#
Description#
脸哥最近在玩一款神奇的游戏,这个游戏里有 n 件装备,每件装备有 m 个属性,用向量zi(aj ,.....,am) 表示 (1 <= i <= n; 1 <= j <= m),每个装备需要花费 ci,现在脸哥想买一些装备,但是脸哥很穷,所以总是盘算着 怎样才能花尽量少的钱买尽量多的装备。对于脸哥来说,如果一件装备的属性能用购买的其他装备组合出(也就是 说脸哥可以利用手上的这些装备组合出这件装备的效果),那么这件装备就没有买的必要了。严格的定义是,如果 脸哥买了 zi1,.....zip这 p 件装备,那么对于任意待决定的 zh,不存在 b1,....,bp 使得 b1zi1 + ... + bpzi p = zh(b 是实数),那么脸哥就会买 zh,否则 zh 对脸哥就是无用的了,自然不必购买。举个例子,z1 =(1; 2; 3);z2 =(3; 4; 5);zh =(2; 3; 4),b1 =1/2,b2 =1/2,就有 b1z1 + b2z2 = zh,那么如果脸哥买了 z1 和 z2 就不会再买 zh 了。脸哥想要在买下最多数量的装备的情况下花最少的钱,你能帮他算一下吗?
Input Format#
第一行两个数 n;m。接下来 n 行,每行 m 个数,其中第 i 行描述装备 i 的各项属性值。接下来一行 n 个数, 其中 ci 表示购买第 i 件装备的花费。
Output Format#
一行两个数,第一个数表示能够购买的最多装备数量,第二个数表示在购买最多数量的装备的情况下的最小花费
Sample Input#
3 3
1 2 3
3 4 5
2 3 4
1 1 2
Sample Output#
2 2
解析#
本题即求所给向量所构成的线性空间中的一个极大线性无关子集,即求解一个整数域线性空间的基,使用高斯消元算法即可。
本题还要求最小花费,可以使用贪心策略,在高斯消元时,每次选择该未知数系数非零时,花费最小的一行用来消元,选作主元即可。
Code:
#include<bits/stdc++.h>
using namespace std;
const int N=520,M=520;
const long double eps=1e-8;
int n,m,c[N],ans,sum;
long double a[N][M];
inline void input(void)
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
scanf("%Lf",&a[i][j]);
for (int i=1;i<=n;i++)
scanf("%d",&c[i]);
}
inline void Guass(void)
{
for (int i=1;i<=m;i++)
{
int row=0;
for (int j=i;j<=n;j++)
if ( fabs(a[j][i]) > eps && ( !row || c[row] > c[j] ) )
row = j;
if ( fabs(a[row][i]) < eps )continue;
if ( row ^ i )swap(a[i],a[row]) , swap(c[i],c[row]);
sum += c[i];ans ++;
for (int j=1;j<=n;j++)
{
if (i==j)continue;
long double rate = a[j][i] / a[i][i];
for (int k=i;k<=m;k++)
a[j][k] -= a[i][k] * rate;
}
}
return;
}
int main(void)
{
input();
Guass();
printf("%d %d\n",ans,sum);
return 0;
}
异或运算#
Description#
给定你由N个整数构成的整数序列,你可以从中选取一些(甚至一个)进行异或(XOR)运算,从而得到很多不同的结果。
请问,所有能得到的不同的结果中第k小的结果是多少。
Input Format#
第一行包含整数T,表示共有T组测试数据。
对于每组测试数据,第一行包含整数N。
第二行包含N个整数(均在1至1018之间),表示完整的整数序列。
第三行包含整数Q,表示询问的次数。
第四行包含Q个整数k1,k2,…,kQ,表示Q个询问对应的k。
Output Format#
对于每组测试数据,第一行输出“Case #C:”,其中C为顺序编号(从1开始)。
接下来Q行描述Q次询问的结果,每行输出一个整数,表示第i次询问中第kiki小的结果。
如果能得到的不同结果的总数少于kiki,则输出“-1”。
Sample Input#
2
2
1 2
4
1 2 3 4
3
1 2 3
5
1 2 3 4 5
Sample Output#
Case #1:
1
2
3
-1
Case #2:
0
1
2
3
-1
解析#
本题的题面很清晰地描述了一个线性空间的样子,但是运算法则是异或,因此,我们就能类似的定义异或意义下的线性空间。
于是,这个异或线性空间的基的求解就和高斯消元算法中的异或高消对应了起来。那么利用异或高消,我们假设求出了(a1,a2,...,an)的表出线性空间的基为(b1,b2,...,bt),设b1>b2>...>bt。
从(b1,b2,...,bt)中取出若干个整数进行异或运算得到数字,显然有2t种取法,即可得到异或空间中有2t个数字。由基的定义可得,(b1,b2,...,bt)可表出的线性空间和(a1,a2,...,an)可表出的线性空间是一样的。
对于主元xi,高斯消元保证了除第i行的xi系数外,其他行的xi系数均为0,又因为b1>b2>...>bt,所以在异或运算上,必然有取b1的比不取b1的大,不取b1时,取b2的比不取b2的大,不取b1和b2时,取b3的比不取b3的大 ... 即异或空间中的最大数是所有bi异或的结果,最小数是0。
那么这就可以和所求第k小数的k联系起来。将k二进制分解,如果k的第i(0≤i<t)位为0,则选bt−i。最后,将所有所选的数异或起来就是异或空间中的第k小数。
事实上,直接对k进行二进制分解不能保证正确性。这样是因为在异或空间中,一定可以异或得到整数0(由某个数异或它本身),而在本题中,不允许这样的操作。所以在高斯消元时可以进行判断,若存在全0行,则说明0本身可以通过若干个不同的数异或得到,最小值为0,需要二进制分解k−1来取数字。反之,则0是实际中不能得到的一个数字,二进制分解k即可。
Code:
#include<bits/stdc++.h>
using namespace std;
const int N=10020,Q=10020;
int n,q,flag,Pow;
unsigned long long a[N];
inline void input(void)
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
scanf("%llu",&a[i]);
}
inline void Guass(void)
{
flag=0 , Pow=n;
for (int i=1;i<=n;i++)
{
for (int j=i+1;j<=n;j++)
if ( a[j] > a[i] )
swap(a[j],a[i]);
if ( !a[i] ){flag=true;Pow=i-1;return;}
for (int j=63;j>=0;j--)
{
if ( a[i] >> j & 1 )
{
for (int k=1;k<=n;k++)
if ( i!=k && a[k] >> j & 1 )
a[k] ^= a[i];
break;
}
}
}
}
inline void solve(void)
{
scanf("%d",&q);
for (int i=1;i<=q;i++)
{
unsigned long long k,ans=0;
scanf("%llu",&k);
if (flag)k--;
if ( k >= (1ULL<<Pow) )
printf("-1\n");
else
{
for (int i=0;i<Pow;i++)
if ( k >> i & 1 )
ans ^= a[ Pow-i ];
printf("%llu\n",ans);
}
}
}
int main(void)
{
int T;
scanf("%d",&T);
for (int i=1;i<=T;i++)
{
input();
Guass();
printf("Case #%d:\n",i);
solve();
}
return 0;
}
<后记>
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】博客园2025新款「AI繁忙」系列T恤上架,前往周边小店选购
【推荐】凌霞软件回馈社区,携手博客园推出1Panel与Halo联合会员
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步