线性基
类似于立体空间向量,只要我们在空间中找到一组基向量i,j,k,以后空间中任何向量都可以用它们表示。
三维欧氏空间是特殊的线性空间,三维欧氏空间的基向量在线性空间中就被推广为了线性基。
称线性空间 V 的一个极大线性无关组为 V 的一组 Hamel 基 或 线性基,简称 基。
(好的以上概念纯扯淡)
我们从实用角度去理解线性基。线性基分为实数线性基和异或线性基,异或线性基更常用。以下均以异或线性基为例。
性质
1.线性基存储的是一个数的集合
2.线性基第
3.设一个数集的最大值为
插入
插入
-
若线性基的第
位为0,则直接在该位插入 , ; -
若线性基的第
位已经有 ,则 ,继续寻找 的最高位重复以上操作。
知道了线性基的实现后,我们就可以简单伪证下它的性质:
1.考虑对于每个
而我们又知道,它插入之前,集合中各元素异或结果就能表示了。则只需要考虑插入它后能否表示它和不同的数的异或和。一定是可以的。给它异或如
同时我们考虑,如果当前数本来就能被异或集合表示出来,则它一定会插入失败,因为一路走来都被抵消了。这也是判断它们能不能表示一个数的方法。
再思考一个问题,如果一个长为
2、3直接根据插入方式显然。
void add(int x){
for(int i=50;i>=0;i--){
if((x>>i)&1){
if(!a[i]){
a[i]=x;
break;
}
x^=a[i];
}
}
}
线性基合并
显然,根据线性基的定义,它有可合并性。只需要把一个线性基里面的东西插入另一个就行了,复杂度
求异或和最值
max:从高位开始贪心。由于线性基第
int solve(){
int ans=0;
for(int i=50;i>=0;i--){
if((ans^a[i])>ans) ans^=a[i];
}
return ans;
}
min:如果出现插入失败,最小异或和就是0。否则,实际上就是线性基最低位的元素(它前面没有元素,且它的最高位最小)。
求排名
求一个数x的排名(这里要求要在线性基内出现过),即把它按位拆解下来,若在遇到一个1,设线性基当前位之前有元素的位的个数为
考虑前面只要有元素的位就可以两两组合共有
另外,对于可重排名,每一个重复元素会对它的排名产生
albus就是要第一个出场
#include<bits/stdc++.h>
using namespace std;
const int mod=10086;
int n,a[105],q,d[105];
void add(int x){
int fl=1;
for(int i=31;i>=0;i--){
if((x>>i)&1){
if(!a[i]){
a[i]=x;
d[i]++,fl=0;
break;
}
x^=a[i];
}
}
if(fl) d[0]++;
}
int qpow(int x,int y){
int res=1;
while(y){
if(y&1) res=res*x%mod;
x=x*x%mod;
y>>=1;
}
return res;
}
int solve(int x){
int ans=0;
for(int i=0;i<=31;i++){
ans+=d[i];
d[i]=ans;
}
ans=0;
for(int i=31;i>=0;i--){
if((x>>i)&1){
if(a[i]) ans+=qpow(2,d[i]-1);
}
}
return ans+1;
}
int main(){
scanf("%d",&n);
for(int i=1,x;i<=n;i++){
scanf("%d",&x);
add(x);
}
scanf("%d",&q);
printf("%d",solve(q)%mod);
return 0;
}
求第k大/小异或和
以第k小为例。对于线性基中第i个有值的位置,比它小的i-1个数无论如何异或和都小于它,且它们最多异或得
那么我们就可以将k拆分二进制,从高位枚举,找到每个1所在的位置,并给答案异或上第i个有值的二进制位。
但问题来了,当答案异或了一个值x时,比x小的数应该按异或x后的值排序,有的数最高位可能改变。问题的根源是,对于一个i最高位的1,它并不是唯一一个在这一位存在1的元素。
线性基不是唯一的。考虑一种构造,使对于
注意是查询之前先重构,特判0的情况。复杂度
void rebuild(){
for(int i=0;i<=30;i++){
for(int j=i-1;j>=0;j--){
if((a[i]>>j)&1) a[i]^=a[j];
}
}
cnt=0;
for(int i=0;i<=30;i++){
if(a[i]) c[++cnt]=a[i];
}
}
int kth(int k){
if(cnt<n) k--;//含0
if((1ll<<cnt)<=k) return -1;//线性基总数不够
rebuild();
int res=0,pos=1;
while(k){
if(k&1) res^=c[pos];
pos++;
k>>=1;
}
return res;
}
前缀线性基
奇技淫巧罢了。
考虑从左往右建立线性基,需要多记录一个pos表示该元素何时被插入。我们查询的时候只需要找到
建立时,每次都在前一次的基础上,如果插入当前位置有数,就交换pos,使位置上记录的尽量是靠后的pos,这样能保证在查询[l,r]时尽可能包含较多的数。
这个需要离线查询。
void insert(ll x,int p){
for(int i=61;i>=0;i--){
if((x>>i)&1){
if(!a[i]){
a[i]=x,pos[i]=p;
break;
}
else if(pos[i]<p){
swap(a[i],x),swap(pos[i],p);
}//还要继续插入他俩的异或和,保证严谨性
x^=a[i];
}
}
}
图上线性基
经典例题:[WC2011] 最大XOR和路径
经典手法:发现如果走过去再走回来,异或和一定会抵消,因此答案一定由一条简单路径和若干环的异或和构成。
那么不妨直接通过跑dfs树,得到环,把环放到线性基里面。然后在线性基里查询每条简单路径能达到的最大异或和。
例题
[bzoj3811] 玛里苟斯
有转化为组合意义的写法,这里提供另一种思路。
难以观察到,最终答案小于
这种情况下,过程中计算结果最多达到
平方项有贡献的概率其实就是
形如
但没想到吧,那样直接计算
注意
即,设线性基元素有
对于输出格式,由于异或和最后一位为1的概率是
//一定要注意过程中有没有炸ll,炸ull的。。
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
using namespace std;
const int maxn=1e5+5;
int n,k,vis[maxn],b[maxn],t[maxn],cnt;
ull a[maxn],r,ans,mod;
void solve1(){
ull ans=0;
for(int i=1;i<=n;i++) ans|=a[i];
if(ans&1) printf("%llu.5",ans/2);
else printf("%llu",ans/2);
}
bool check(int x,int y){
for(int i=1;i<=n;i++){
if(((a[i]>>x)&1)&&!((a[i]>>y)&1)) return 0;
if(((a[i]>>y)&1)&&!((a[i]>>x)&1)) return 0;
}
return 1;
}
void solve2(){
for(int i=1;i<=n;i++){
for(int j=0;j<63;j++){
if((a[i]>>j)&1) vis[j]=1;
}
}
for(int i=0;i<33;i++){
if(vis[i]) ans+=(1llu<<(i+i));
}
for(int i=0;i<33;i++){
for(int j=i+1;j<33;j++){
if(vis[i]&&vis[j]){
ans+=(1llu<<(i+j));
if(check(i,j)) ans+=(1llu<<(i+j));
}
}
}
if(ans&1) printf("%llu.5",ans/2);
else printf("%llu",ans/2);
}
void add(int x){
for(int i=22;i>=0;i--){
if((x>>i)&1){
if(!b[i]){
b[i]=x;
break;
}
else x^=b[i];
}
}
}
void calc(int x){
ull tx=x/mod,tr=x%mod;
for(int i=1;i<k;i++){
tx*=x,tr*=x;
tx+=tr/mod;
tr%=mod;
}
// printf("Res: %llu\n",res);
ans+=tx,r=tr+r;
ans+=r/mod,r%=mod;
}
void dfs(int x,int sum){
if(x==cnt+1){
calc(sum);
return ;
}
dfs(x+1,sum);
dfs(x+1,sum^t[x]);
}
void solve3(){
for(int i=1;i<=n;i++) add(a[i]);
for(int i=22;i>=0;i--) if(b[i]) t[++cnt]=b[i];
// printf("%d\n",cnt);
mod=(1<<cnt);
dfs(1,0);
if(r) printf("%llu.5",ans);
else printf("%llu",ans);
}
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++){
scanf("%llu",&a[i]);
}
if(k==1) solve1();
else if(k==2) solve2();
else solve3();
return 0;
}
[bzoj3569] DZY Loves Chinese II
如果不强制在线,可以线段树分治,但奈何时间复杂度过高。
正解是奇妙构造。(类似于这种多组询问图的连通性的问题好像都是hash构造,比如 [CSP-S 2022] 星战。)
考虑若一个图不联通,则必然会被分割成两个块且其间没有相连的边。如果这两个块之间相连的边集为
不妨考虑随机异或值哈希,如果构造出边的权值,使得
考虑随便对图跑一个dfs树,若要断成两个块,则必须至少断掉一个树边,且穿过该树边的非树边也要断了。不妨直接给非树边随便赋权值,树边权值为穿过它的非树边的权值异或和。最后在线性基上判断是否有一个组合能使异或和为0。
操作的时候直接给当前边赋值为它所有儿子的边权异或和,如果为非树边,则正是我们所需要的;如果为树边,它的边权也为穿过它的非树边的异或和,若有穿过它但没穿过当前边的非树边,会在异或时抵消。(画几个图看看就出来了)
[bzoj4184] shallot
听说线性基好像可以处理删除操作,但要记录一堆神仙玩意,没太看懂。
这个题因为可以离线,故使用线段树分治即可。太卡空间。
[BeiJing2011] 梦想封印
仔细想想好像也不难,但是死活没意识到线性基最多插入log次,可以暴力重构。
总体就是套路的叠加。删边改加边,线性基图上路径长问题,分别维护链和环,然后线性基插入成功最多log次,可以暴力重构。
我不会的点在于如何维护路径的不同值?这里用set维护对于每条路经,它和环的线性基的异或最小值。这里就用到线性基的不唯一性,不妨固定一个统一标准,都把路径权值搞成最小值,就能比较出来有没有重复的。
[SCOI2016] 幸运数字
线性基+树剖+线段树的维护看起来很炸裂,至少得4个log吧。
如果倍增就能3个log,差不多能过。
还有更优解,是双log的,就是用线性基维护树上前缀和的做法。就是前缀线性基,然后每次判断dep和pos的关系去操作。
本文作者:YYYmoon
本文链接:https://www.cnblogs.com/YYYmoon/p/18679209
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步