线性基
线性基
性质
- 原数集中的数都可以由线性基中的一些数异或得到
- 原数集中的数字异或出来的值域与线性基中的元素异或出来的值域相等
- 任取若干个原数集的数, 把他们写成线性基中元素 XOR 的形式, 然后再异或起来, 显然线性基中元素剩下 0/1 个
- 线性基中的每个元素的二进制最高位均不同
构造
a XOR b = c, 则 a = b XOR c
void ins(LL x)
{
for (int i = 50; i >= 0; -- i)
{
if (!(x & (1LL << i))) continue;
if (p[i]) x ^= p[i];
else
{
p[i] = x;
break;
}
}
}
应用
求原数集最大异或和
选一些数使他们异或和最大
LL getmax()
{
LL ret = 0;
for (int i = 50; i >= 0; -- i)
if ((ret ^ p[i]) > ret) ret ^= p[i];
return ret;
}
线性无关的方程
联系高斯消元, 性质和线性基非常相似,
每一个主元的列上都只有他这个非0, 其余都是0,也就是主元所在的这一行向量, 作为这一位(主元的列号)的基,
插入过程中异或的消去, 改成暴力的高斯消元即可
见下面例题
例题
BZOJ3105: [cqoi2013]新Nim游戏 / BZOJ2460: [BeiJing2011]元素
本题的游戏稍微有些不同:在第一个回合中,第一个游戏者可以直接拿走若干个整堆的火柴。可以一堆都不拿,但不可以全部拿走。第二回合也一样. 从第三个回合(又轮到第一个游戏者)开始,规则和Nim游戏一样。
如果你先拿,怎样才能保证获胜?如果可以获胜的话,还要让第一回合拿的火柴总数尽量小。
Sol:
即拿走和尽量小的元素, 使得剩下的元素中任意取的异或和都不是0
即取和尽量大的元素, 使得这些元素任意取异或和都不是0
首先对已经取过的数构造线性基, 只有能插入成功的数才可以取, 否则存在之前取过的数异或起来等于这个数
然后想到从大往小取, 打完交上去A了, 突然发现这样贪心感觉有问题?
证明如果能成功插入就一定要插入;
若不插入, 则选了一些这个数之后的一些数, 并且所有的数可以异或得到此数(若不能得到那就可以插入了)
假设\(a_{k_1} ~xor ~a_{k_2}~ \dots ~ xor ~ a_{k_n} = x\), 那么一定有一部分是来自后面的数, 因为单单由前面的数异或不出来, 那么就可以将 \(x\) 移向到左边, 替换掉某个后面的数, 这样就不能插入那个后面的数, 然后答案就更优了
BZOJ2115: [Wc2011] Xor <线性基 + 图>
不知道如何理性证明
BZOJ4004[JLOI2015]装备购买
题意:
有 \(n\) 个向量, 每个有花费, 求出选出最多的线性无关的向量, 并且花费最小
Sol:
线性无关就是高斯消元之后非0行的数量
贪心从小到大插入即可, 因为高斯消元和顺序无关
const int MAXN = 5e2 + 10;
const long double EPS = 1e-8;
int n, m;
long double tmp[MAXN];
namespace Lbase
{
bool vis[MAXN];
long double p[MAXN][MAXN];
bool insert(long double * a)
{
for (int i = 1; i <= m; ++ i)
{
if (fabs(a[i]) > EPS)
{
if (vis[i])
{
long double k = a[i] / p[i][i] ;
for (int j = 1; j <= m; ++ j) a[j] -= k * p[i][j];
}
else
{
vis[i] = true;
for (int j = 1; j <= m; ++ j) p[i][j] = a[j];
return true;
}
}
}
return false;
}
}
struct Val
{
int id, c;
long double v[MAXN];
} z[MAXN];
bool cmp(Val a, Val b)
{
return a.c < b.c;
}
int main()
{
n = in(); m = in();
for (int i = 1; i <= n; ++ i)
{
z[i].id = i;
for (int j = 1; j <= m; ++ j)
scanf("%Lf", &z[i].v[j]);
}
for (int i = 1; i <= n; ++ i) z[i].c = in();
sort(z + 1, z + n + 1, cmp);
int ans1 = 0, ans2 = 0;
for (int i = 1; i <= n; ++ i)
{
for (int j = 1; j <= m; ++ j) tmp[j] = z[i].v[j];
if (Lbase::insert(tmp)) ++ ans1, ans2 += z[i].c;
}
printf("%d %d\n", ans1, ans2);
return 0;
}