线性基,你太美
线代科技——线性基
Part 0.整活
今天我们学的是线性基你太美。
Part 1.什么是线性基?
这是oiwiki的解释。众所周知,oiwiki的文字描述一般不是人话。
简单的理解:线性基是针对一个序列 $ A $ 所生成的一个数的集合 ,其需要满足以下要求:
- 线性基中任意选择一些数异或起来所形成的集合,与原序列中任意选择一些数异或起来所形成的集合相等。
- 线性基是满足上述要求的最小集合。
一条比较重要的性质:线性基中不存在一组数,使得它们的异或值为0(解释:如果存在 $ x \ xor \ y = 0 $ ,那么显然 $ x = y $ ,删去 $ y $ 后对线性基没有影响,但存在 $ y $ 的线性基也就意味着不是最小集合,与其要求矛盾)。
Part 2.如何构造出线性基
存在两种方法:
- 贪心构造;
- 高斯消元法。
主要解释贪心法构造:
根据线性基的定义可以得出,存在一中线性基的构造为原序列( $ A $ )的一个子集。那么就可以考虑 $ A $ 中哪些元素是可以删去的,显然如果存在一个数 $ x \in A $ ,其可以由 $ k $ 个 $ A $ 中的数异或出来,那显然是可以删掉的,再结合二进制,我们就可以得到下面这个神奇的构造方法:
- 向已存在的不完全的线性基集合( $ P $ )中加入一个来自 $ A $ 集合的数 $ x $ 。
- 对于 $ x $ 从高到低遍历其二进制位,如果遍历到第 $ i $ 位为 $ 1 $ ,则判断插入第 $ i $ 位为 $ 1 $ 的数是否出现过(储存在 $ P_{i} $ ):
- 如果出现过,则将 $ x $ 异或上 $ P_{i} $ ,接着向低位遍历;
- 如果没出现过,则将当前数赋值给 $ P_{i} $ ,表示现在出现了,并退出。
显然存在一个性质, $ P_{i} $ 的最高二进制位为 $ i $ 。
Q:为什么这样构造?
首先,对于一个集合 $ S $ 中的任意两个数 $ x $ 和 $ y $ ,它们相互异或产生的值只有三种,即 $ x $ , $ y $ ,$ x \ xor \ y $ 。
也就是说把两个 $ S $ 的子集中的数任意互相异或,再把两个子集合并得到的子集 能异或出的数的集合 $ B_{1} $ 与把任意互相异或之前的子集合并得到的子集 能异出的数的集合 $ B_{2} $ 相等。
于是有个结论:把原集合 $ S $ 中的数任意互相异或,集合S能异或出的数的集合 $ B $ 不变。
由于我们构造线性基的方式中,每个
$ P_{i} $ 都是原集合𝑆中若干个数的异或和,所以集合 $ P $ 能异或出的数的集合与原集合 $ 𝑆 $ 能异或出的数的集合相等。
对于一个可以由原序列中一些数异或出来的数 $ x \in A $ ,显然其在插入的过程中会被从高位到底位地不断异或,如果最后都没能给一个 $ P_{i} $ 赋值,就说明集合 $ P $ 中现存的数已经能将 $ x $ 异或出来了,那么 $ x $ 也就没有存在的必要了。也就是未能被加入线性基 $ P $ 。
这样,当我们将所有数都插入线性基 $ P $ 时,也就能用该基表示能异或出的所有数了。同时根据贪心的构造方式可以知道: $ P.size()<=A.size() $ 。
Part 3.code
非常朴实,简单易上手。
void ins(int x)
{
for(int i=55;i>=0;i--)
{
if(!(x&(1ll<<i)))continue;
if(p[i])x^=p[i];
else{p[i]=x;return;}
}
}
Part 4.一些例题
1. 洛谷P3812 【模板】线性基
模板题,实至名归。
线性基板子+一个很显然地贪心。对于已经构造出来的线性基 $ P $ ,暴力枚举一遍每个元素,并尝试异或,如果比 $ ans $ 大就更新答案即可。
#include <bits/stdc++.h>
#define i8 __int128
#define int long long
#define fuck inline
#define lb long double
using namespace std;
// typedef longlong ll;
const int N=1e2+10,M=5e5+520,mod=1114514;
const int inf=INT_MAX,INF=1e9+7;
// const int mod1=469762049,mod2=998244353,mod3=1004535809;
// const int G=3,Gi=332748118;
// const int M=mod1*mod2;
fuck int read()
{
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-'){f=-1;}c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c-'0');c=getchar();}
return x*f;
}
fuck void write(int x)
{
if(x<0){putchar('-');x=-x;}
if(x>9) write(x/10);
putchar(x%10+'0');
}
int n;
int a[N],p[N];
fuck void ins(int x)
{
for(int i=55;i>=0;i--)
{
if(!(x&(1ll<<i)))continue;
if(p[i])x^=p[i];
else{p[i]=x;return;}
}
}
fuck void solve()
{
int ans=0;
cin>>n;
for(int i=1;i<=n;i++)a[i]=read(),ins(a[i]);
for(int i=55;i>=0;i--)if((ans^p[i])>ans)ans^=p[i];
cout<<ans<<"\n";
}
signed main()
{
// ios::sync_with_stdio(false);
// cin.tie(0); cout.tie(0);
// int fuckccf=read();
// int QwQ=read();
// while(QwQ--)solve();
solve();
return 0;
}
// 6666 66666 666666
// 6 6 6 6 6
// 6 6 6666 6
// 6 6 6 6 6
// 6666 6 6 6666666
2.洛谷 P4570 [BJWC2011] 元素
直接考虑贪心,按Magic值进行排序,Magic值从高到低的选取Number值尝试插入线性基,如果插入成功则说明不会使Magic值消失,直接加上;反之,则不能加入。
#include <bits/stdc++.h>
#define i8 __int128
#define int long long
#define fuck inline
#define lb long double
using namespace std;
// typedef long long ll;
const int N=2e7+23,M=5e5+520,mod=1114514;
const int inf=INT_MAX,INF=1e9+7;
// const int mod1=469762049,mod2=998244353,mod3=1004535809;
// const int G=3,Gi=332748118;
// const int M=mod1*mod2;
fuck int read()
{
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-'){f=-1;}c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c-'0');c=getchar();}
return x*f;
}
fuck void write(int x)
{
if(x<0){putchar('-');x=-x;}
if(x>9) write(x/10);
putchar(x%10+'0');
}
int n,p[N];
struct node
{
int id,w;
}a[N];
fuck bool cmp(node x,node y){return x.w>y.w;}
fuck bool ins(int x)
{
for(int i=63;i>=0;i--)
{
if(!(x&(1ll<<i)))continue;
if(p[i])x^=p[i];
else{p[i]=x;return 1;}
}
return 0;
}
fuck void solve()
{
cin>>n;int ans=0;
for(int i=1;i<=n;i++)cin>>a[i].id>>a[i].w;
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;i++)if(ins(a[i].id))ans+=a[i].w;
cout<<ans<<"\n";
}
signed main()
{
// ios::sync_with_stdio(false);
// cin.tie(0); cout.tie(0);
// int fuckccf=read();
// int QwQ=read();
// while(QwQ--)solve();
solve();
return 0;
}
// 6666 66666 666666
// 6 6 6 6 6
// 6 6 6666 6
// 6 6 6 6 6
// 6666 6 6 6666666
3. 洛谷 P4301 [CQOI2013] 新Nim游戏
知识点:线性基+贪心+Nim游戏
题目要求我们保证先手必赢,也就是需要我们使得两个人第一回合取完以后所剩的数量异或和不为 $ 0 $ ,且先手所取的数量和最小。看似无法确定取哪些应该取,实际上我们可以惊奇地发现,其要求剩下所有数异或和不为 $ 0 $ ,正好符合线性基的性质(线性基内任意几个不相同的数异或和不为 $ 0 $ ),这样我们可以用线性基去处理答案。如果当前数无法被插入,说明当前数与线性基内某些数异或和为 $ 0 $ (称这些数为有关数),如果不把其取走,那么后手将除有关数外的所有数取走,则会使剩下的数异或和为 $ 0 $ ,使得先手必败,所以对于这样的一个当前数,必须取走。由于题目要求最小值,所以从大到小地插入,尽量不动大数。
#include <bits/stdc++.h>
#define i8 __int128
#define int long long
#define fuck inline
#define lb long double
using namespace std;
// typedef long long ll;
const int N=2e7+23,M=5e5+520,mod=1114514;
const int inf=INT_MAX,INF=1e9+7;
// const int mod1=469762049,mod2=998244353,mod3=1004535809;
// const int G=3,Gi=332748118;
// const int M=mod1*mod2;
fuck int read()
{
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-'){f=-1;}c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c-'0');c=getchar();}
return x*f;
}
fuck void write(int x)
{
if(x<0){putchar('-');x=-x;}
if(x>9) write(x/10);
putchar(x%10+'0');
}
int p[N];
fuck bool ins(int x)
{
for(int i=63;i>=0;i--)
{
if(!(x&(1ll<<i)))continue;
if(p[i])x^=p[i];
else{p[i]=x;return 1;}
}
return 0;
}
int n,a[N];
fuck bool cmp(int x,int y){return x>y;}
fuck void solve()
{
cin>>n;
int ans=0;
for(int i=1;i<=n;i++)cin>>a[i];
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;i++)
if(!ins(a[i]))ans+=a[i];
cout<<ans<<"\n";
}
signed main()
{
// ios::sync_with_stdio(false);
// cin.tie(0); cout.tie(0);
// int fuckccf=read();
// int QwQ=read();
// while(QwQ--)solve();
solve();
return 0;
}
// 6666 66666 666666
// 6 6 6 6 6
// 6 6 6666 6
// 6 6 6 6 6
// 6666 6 6 6666666
4. 洛谷 P3857 [TJOI2008] 彩灯
这道题让我感受到了long long的防不胜防。
稍微一点思维吧。首先,原题目中的O和X太难看了,作为 $ OIer $ ,我们把它们分别替换为 $ 1 $ 和 $ 0 $ ,而开关操作就相当于异或。把玩一下样例,可以发现第一行 $ 11 $ 其实没有存在的必要,其可以由 $ 10 $ 和 $ 01 $ 异或得到,就可以把第一行踢出考虑。这种感觉就和线性基的构造很像,而恰好线性基里的任意个不同元素异或和不为 $ 0 $ ,也就是说线性基内任意不同元素异或的结果都不相同,这可以看做是一种方案,且不会与此线性基内其他组合所得到的结果相同。这样对于一个大小为 $ ans $ 的线性基,其最终答案为 $ 2^{ans} $ 。注意开 $ long long $ 。
#include <bits/stdc++.h>
#define i8 __int128
#define int long long //记得开long long
#define fuck inline
#define lb long double
using namespace std;
// typedef long long ll;
const int N=100+23,M=5e5+520,mod=2008;
const int inf=INT_MAX,INF=1e9+7;
// const int mod1=469762049,mod2=998244353,mod3=1004535809;
// const int G=3,Gi=332748118;
// const int M=mod1*mod2;
fuck int read()
{
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-'){f=-1;}c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c-'0');c=getchar();}
return x*f;
}
fuck void write(int x)
{
if(x<0){putchar('-');x=-x;}
if(x>9) write(x/10);
putchar(x%10+'0');
}
int p[N];
fuck void ins(int x)
{
for(int i=55;i>=0;i--)
{
if(!(x&(1ll<<i)))continue;
if(p[i]){x^=p[i];}
else{p[i]=x;return;}
}
}
int n,m,a[N];
fuck int toint(string s)//把字符串搞成整数
{
int ans=0;
for(int i=0;i<s.size();i++)
{
if(s[i]=='O')ans+=(1ll<<i);
else continue;
}
return ans;
}
fuck void solve()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
string s;cin>>s;
ins(toint(s));
}
int ans=0;
for(int i=55;i>=0;i--)if(p[i])ans++;//统计线性基大小
cout<<(1ll<<ans)%mod<<"\n";
}
signed main()
{
// ios::sync_with_stdio(false);
// cin.tie(0); cout.tie(0);
// int fuckccf=read();
// int QwQ=read();
// while(QwQ--)solve();
solve();
return 0;
}
// 6666 66666 666666
// 6 6 6 6 6
// 6 6 6666 6
// 6 6 6 6 6
// 6666 6 6 6666666
完结收工!!!!!

看完点赞,养成习惯
\(\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\)

浙公网安备 33010602011771号