线性基
定义
线性基是一个集合,每个序列都拥有至少一个线性基,取线性基中若干个数异或起来可以得到原序列中的任何一个数。
性质
1、原序列任意一个数可以由线性基里若干个数异或得到
2、线性基里任意取数异或起来不可能为 0
3、对于一个序列,线性基不一定唯一,但线性基中数的个数固定且在满足性质 1 的条件下最小
4、对于一个值域为 \([1,N]\) 的序列,线性基的长度小于等于 \(log_2 N\)
线性基构造
void ins(int x){
for(int i=M;i>=0;i--){
if(x&(1ll<<i)){
if(d[i])x^=d[i];
else{
d[i]=x;
break;
}
}
}
}
由此可知线性基中如果 \(d_i\) 不为空,则 \(d_i\) 的二进制第 \(i+1\) 位为 \(1\) ,且为最高位;记为性质 5
性质的证明
gu....
求最大值
即求原序列中若干数异或和最大
由性质 5 易得方法为:
从线性基的最高位开始,假如当前的答案异或线性基的这个元素可以变得更大,那么就异或它
int ma(){
int ans=0;
for(int i=M;i>=0;i--) {
if((ans^d[i])>ans)ans^=d[i];
}
return ans;
}
最小值
如果有元素不能插入线性基,最小值就是 0 ,否则为最小的 \(d_i\)
K 小值
把线性基处理一下,使得从小到大枚举 \(d\) 异或 \(d_i\) 一定变大
操作为若 存在 \(j\) 满足 \(j<i\ and\ d_j=1\) ,则让 \(d_i\) 在第 \(j\) 位为 \(0\)
处理完之后 \(d\) 还是原序列线性基,注意判断原序列异或能否为 \(0\) ,即有没有不能插入的数
void rebuild(){
for(int i=0;i<=60;i++){
for(int j=0;j<i;j++){
if(d[i]&(1ll<<(j)))d[i]^=d[j];
}
if(d[i])tot++;
}
}
int k_th(int k){
if(tot<n)k--;
if(k>=(1ll<<tot))return -1;
int ans=0;
for(int i=0;i<=60;i++){
if(!d[i])continue;
if(k&1)ans^=d[i];
k/=2;
}
return ans;
}
删除
题
模板
新Nim游戏
Nim 游戏为序列异或和不为 0 则先手必胜。
所以本题第一步要使得对方无法构造出异或和为 0 的序列。
想到线性基的性质 2 所以要将不能插入线性基的数全部拿走。
为了最小化,将序列从大到小排序后插入;
为什么是对的?
感性理解因为如果原序列有两个数拥有相同的二进制位为 1 ,它们都可以插入线性基,但此时插入更大的显然是不劣且对其它操作没有影响的。
元素
板子题
彩灯
用开关序列构造线性基,如果某一位有就说明这个彩灯能被打开,记录能打开的彩灯数量 \(k\) ,答案即为 \(2^k\)
原理:线性基的性质保证能够得到原序列的所有可能异或和,同时不会重复。
最大异或和路径
发现可以重复走,所以我们除了从 \(1\) 走到 \(n\) 外还可以走一些 环,发现走到环的起点再回来会经过“链接路径”两次,所以不需要管。所以答案就是在所有环、所有 \(1-n\) 路径里找异或最大值。
所有环好找,但所有 \(1-n\) 路径不好找,但我们发现两条路径实际就是一个环,所以我们随便选一条路径即可。把环和路径的异或和构造线性基即可。
然后发现其实以一个点出发找到的环不是所有的环,但就异或来讲可以得到所有异或值,所以方法正确
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+10;
int n,m,head[N],idx;
struct edge{
int w,v,next;
}e[N<<1];
void con(int u,int v,int w){
idx++;e[idx].v=v;e[idx].next=head[u];e[idx].w=w;head[u]=idx;
}
int d[63];
void ins(int x){
for(int i=60;i>=0;i--){
if(x&(1ll<<i)){
if(d[i])x^=d[i];
else {d[i]=x;return;}
}
}
}
int vis[N],pre[N];
void dfs(int u,int sum){
pre[u]=sum,vis[u]=1;
for(int i=head[u];i;i=e[i].next){
int v=e[i].v,w=e[i].w;
if(!vis[v])dfs(v,sum^w);
else {ins(w^sum^pre[v]);}
}
}
signed main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
int u,v,w;
cin>>u>>v>>w;
con(u,v,w),con(v,u,w);
}
dfs(1,0);
int ans=pre[n];
for(int i=60;i>=0;i--){
if((ans^d[i])>ans)ans^=d[i];
}
cout<<ans;
return 0;
}
albus就是要第一个出场
很好求去重后每个数的排名,像这样:
for(int i=0,j=0;i<=60;i++){
if(!d[i])continue;
if(b&(1ll<<i))ans=(ans+(1ll<<j))%mod;
j++;
}
排名即为 \(ans+1\)
怎么求原序列呢,结论:每个异或值出现 \(2^{n-k}\) 次,其中 \(k\) 是线性基元素个数
简单证明:对于无法插入线性基的数的集合,随便选一个子集有 \(2^{n-k}\) 种,每个子集的异或和都可与线性基内的一些异或和异或得到 \(0\) ,然后用 \(0\) 去异或所有异或值,所以每个异或值有 \(2^{n-k}\) 种组成方式。
解法显然
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+10,mod=10086;
int n,b,tot,d[62],p[62];
int ksm(int a,int b){
int ans=1;
while(b){
if(b&1)ans*=a;
a*=a;a%=mod;ans%=mod;b>>=1;
}return ans;
}
void ins(int x){
for(int i=60;i>=0;i--){
if(x&(1ll<<i)){
if(d[i])x^=d[i];
else {tot++,d[i]=x;return;}
}
}
}
signed main(){
cin>>n;
for(int i=1;i<=n;i++){
int u;cin>>u;ins(u);
}
cin>>b;
int ans=0;
for(int i=0,j=0;i<=60;i++){
if(!d[i])continue;
if(b&(1ll<<i))ans=(ans+(1ll<<j))%mod;
j++;
}
cout<<(ans*ksm(2ll,n-tot)%mod+1ll)%mod<<" ";
return 0;
}
前缀线性基
用于处理一段区间里异或值
思路就是对于每个 \(i\) 记录 \([1,i]\) 的线性基,同时保证尽可能用靠 \(i\) 的数作为基;
为了确定一个基在某个区间内,还要记录这个基是由哪个数形成的,记为 \(pos\)
void ins(int x){
num++;
for(int i=0;i<=60;i++){
d[num][i]=d[num-1][i];
pos[num][i]=pos[num-1][i];
}
int p=num;
for(int i=60;i>=0;i--){
if(x&(1ll<<i)){
if(d[num][i]){
if(pos[num][i]<p){
swap(pos[num][i],p);
swap(d[num][i],x);
}
x^=d[num][i];
}
else{
d[num][i]=x;
pos[num][i]=p;
return;
}
}
}
}
幸运数字
前缀线性基,不过把 \(d,pos_{u,i}\) 定义改为根节点到 \(u\) 的线性基
求 \(lca\) ,深度小于 \(dep_{lca}\) 就不要,每次开一个临时线性基暴力合并 \(u,v\) 即可
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
con(u,v),con(v,u);
}
dfs(1,0);
for(int i=1;i<=q;i++){
int u,v;cin>>u>>v;
int lc=lca(u,v),ans=0;
for(int i=60;i>=0;i--){
now[i]=0;
if(dep[pos[u][i]]>=dep[lc])now[i]=d[u][i];
}
for(int i=60;i>=0;i--){
if(pos[v][i]<dep[lc])continue;
int x=d[v][i];
if(!x)continue;
for(int j=i;j>=0;j--){
if(x&(1ll<<j)){
if(now[j]) x^=now[j];
else{now[j]=x;break;}
}
}
}
for(int i=60;i>=0;i--)
if((ans^now[i])>ans)ans^=now[i];
cout<<ans<<"\n";
}

浙公网安备 33010602011771号