『线性空间 整数线性基和异或线性基』
<更新提示>
<第一次更新>
<正文>
线性空间
定义
线性空间是一个关于一下两个运算封闭的向量集合:
\(1.\)向量加法\(a+b\),其中\(a,b\)为向量
\(2.\)标量乘法\(k*a\),其中\(a\)为向量,\(k\)为常数
基础概念
\(1.\)给定若干个向量\(a_1,a_2,...,a_n\),若向量\(b\)能够通过\(a_1,a_2,...,a_n\)经过向量加法和标量乘法得到,则称向量\(b\)能够通过\(a_1,a_2,...,a_n\)表出。
\(2.\)对于向量\(a_1,a_2,...,a_n\)能够表出的所有向量所构成了一个线性空间,称\(a_1,a_2,...,a_n\)为这个线性空间的生成子集。
\(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
解析
本题的题面很清晰地描述了一个线性空间的样子,但是运算法则是异或,因此,我们就能类似的定义异或意义下的线性空间。
于是,这个异或线性空间的基的求解就和高斯消元算法中的异或高消对应了起来。那么利用异或高消,我们假设求出了\((a_1,a_2,...,a_n)\)的表出线性空间的基为\((b_1,b_2,...,b_t)\),设\(b_1>b_2>...>b_t\)。
从\((b_1,b_2,...,b_t)\)中取出若干个整数进行异或运算得到数字,显然有\(2^t\)种取法,即可得到异或空间中有\(2^t\)个数字。由基的定义可得,\((b_1,b_2,...,b_t)\)可表出的线性空间和\((a_1,a_2,...,a_n)\)可表出的线性空间是一样的。
对于主元\(x_i\),高斯消元保证了除第\(i\)行的\(x_i\)系数外,其他行的\(x_i\)系数均为\(0\),又因为\(b_1>b_2>...>b_t\),所以在异或运算上,必然有取\(b_1\)的比不取\(b_1\)的大,不取\(b_1\)时,取\(b_2\)的比不取\(b_2\)的大,不取\(b_1\)和\(b_2\)时,取\(b_3\)的比不取\(b_3\)的大 \(...\) 即异或空间中的最大数是所有\(b_i\)异或的结果,最小数是\(0\)。
那么这就可以和所求第\(k\)小数的\(k\)联系起来。将\(k\)二进制分解,如果\(k\)的第\(i(0 \leq i<t)\)位为\(0\),则选\(b_{t-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;
}
<后记>