RP_INT_MAX
2022-08-11 20:31:27
Upd on 2022.8.19:添加线性基的求交与求并。
Upd on 2023.5.9:日报过审,于是修了格式,改了一些以前写的过于迷惑的语言表述。
Upd on 2023.5.12:采纳魏老师建议,添加带删除线性基部分,同时增加一道例题。
线性基作为一个工具,在很多跟异或有关的题目中都有广泛的应用。
提前介绍一个异或(用
若
因此,若
定义:给定数集
通俗地说,线性基是一个数的集合。每个序列都拥有至少一个线性基。取线性基中若干个数异或起来可以得到原序列中的任何一个数。
原序列的任意一个数都可以由线性基内部的一些数异或得到。
线性基内部的任意数异或起来都不能得到
线性基内部的数个数唯一;且在保持性质一的前提下,数的个数是最少的。
下面给出三个性质的证明。
用反证法,设
故假设不成立,线性基中不存在任何数异或起来可以得到
证毕。
对将插入的数
与
由于有复制操作,为方便,使用结构体。
struct node {
ll p[60];
node() {memset(p,0,sizeof p);}
};
node d,all;
node merge(const node&a,const node&b) {
node res;
d=a,all=a; //all表示目前所有可用的低位基
ll k; //k是把Bi和它的低位削减至0所用到的A的异或和
for(int i=0;i<60;++i) {
if(!b.p[i]) continue;
ll v=b.p[i];
k=0;
int j;
for(j=i;j>=0;--j)
if(1LL<<j&v) {
if(all.p[j]>0)
v^=all.p[j],k^=d.p[j];
else break;
}
if(!v) res.p[i]=k;
else all.p[j]=v,d.p[j]=k;
}
return res;
}
采纳魏老师建议,增加此版块。
在线的不太会就不写了,也算是给后人留个坑填嘛(
将操作离线,维护
与不带删除的线性基基本类似。根据贪心思想,我们希望能使高位基的删除时间较晚(否则,到了删除时间,高位基无法选择,会使答案变劣)。
因此,从插入的数的高位开始枚举,能插就插。
void upd(ll x,int time) { //插入数x,该数最晚删除时间为time
for(int i=60;i>=0;--i)
if(x>>i&1) {
if(t[i]<time) {
swap(time,t[i]);
swap(x,p[i]);
}
if(time==0) break;
x^=p[i];
}
}
这里的“最大值”含义上文已有描述,故不再过多赘述。
只需在不带删除的线性基的基础上加上一个关于时间的特判。
ll getmx(int time) { //查询当前时间为time时的最大值
ll ans=0;
for(int i=60;i>=0;--i)
if(time<t[i])
if((ans^p[i])>ans) ans^=p[i];
return ans;
}
这里介绍一下线性基的复杂度。
插入:upd()
函数从
求最大值:读入数据需要
求最小值:检查需要
求 prework()
函数较为耗时。总复杂度为
判断数
求并:很显然,
求交:从代码里可看出,两重循环,故也是
显然,需要一个构造线性基的辅助数组,空间复杂度为
这里,以两道例题,让读者更加熟悉线性基的用法。
以下每一道例题,笔者的讲解都比较简略,故给出完整的参考代码以加深理解。如果读者觉得难以接受,可以参看对应题目的题解。
还是选主题库的题吧,怕被没绑账号的人喷,想要做非主题库题目,可以参考后面给的习题单(
算法分析:线性基,排序,贪心。
首先按照矿石的
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
struct node {
ll a;
int b;
friend bool operator< (const node &a,const node &b) {
return a.b>b.b;
}
} a[1010];
ll p[100];
int n,ans;
inline void upd(ll x,int y) {
for(int i=60;~i;--i)
if(x>>i&1) {
if(!p[i]) {p[i]=x,ans+=y;break;}
x^=p[i];
}
}
int main () {
cin>>n;
for(int i=1;i<=n;++i) cin>>a[i].a>>a[i].b;
sort(a+1,a+1+n);
for(int i=1;i<=n;++i) upd(a[i].a,a[i].b);
cout<<ans<<endl;
return 0;
}
算法分析:线性基,字符串。
这是一题很明显的线性基,由于线性基可以通过异或的方式,得到原序列中的任意元素,所以我们只要直接求线性基就可以了。设线性基的长度为
#include <string>
#include <cstdio>
#include <iostream>
using namespace std;
typedef long long ll;
string s;
ll p[100];
inline void upd(ll x) {
for(int i=60;~i;--i)
if(x&1LL<<i)
if(p[i]) x^=p[i];
else {p[i]=x;break;}
}
int n,m;
int main () {
cin>>n>>m;
while(m--) {
cin>>s;
ll x=0;
for(int i=0;i<n;++i) x|=(ll)(s[i]=='O')<<i;
upd(x);
}
int ans=0;
for(int i=60;~i;--i)
if(p[i]) ++ans;
cout<<(1LL<<ans)%2008;
return 0;
}
有的时候,题目里面不仅仅有线性基,而更是将其与树论、图论等知识结合起来,这就要求我们不仅会理论知识,而且要触类旁通。
下面,给出三道更难的例题。
算法分析:线性基,排序,博弈论。
显然,根据小学奥数知识根据 Nim 游戏的结论,不能给后手任何让石子数异或为
关于最小值的问题,跟例一一样,先排序即可。
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
int n,a[110],b[110];
ll s,ans;
int main () {
cin>>n;
for(int i=1;i<=n;++i)
cin>>a[i],s+=1LL*a[i];
sort(a+1,a+1+n);
for(int i=n;i;--i) {
int t=a[i];
for(int j=30;~j;--j)
if(a[i]>>j&1)
if(b[j]) a[i]^=b[j];
else {b[j]=a[i];break;}
if(a[i]) ans+=1LL*t;
}
cout<<s-ans;
return 0;
}
算法分析:线性基,图形结构,DFS。
先假设选择了一条从
因为图是联通的,所以可以经过任意环,把所有的环的异或值扔到线性基里,然后再考虑选择哪一条路径。注意到,若从
如何找环?其实很简单,通过在 DFS 树上通过返祖边找到简单环的方法,很容易能找到环。
#include<iostream>
using namespace std;
typedef long long ll;
int hd[100010],num;
struct node {
int next,to;
ll v;
} edg[200010];
inline void add(int fr,int to,ll v) {
edg[++num].to=to;
edg[num].v=v;
edg[num].next=hd[fr];
hd[fr]=num;
}
ll b[100];
inline void upd(ll x) {
for(int i=60;~i;--i)
if(x&1LL<<i)
if(b[i]) x^=b[i];
else {b[i]=x;break;}
}
ll d[50010];
bool vis[50010];
void DFS(int x) {
vis[x]=1;
for(int i=hd[x];i;i=edg[i].next) {
int v=edg[i].to;
if(vis[v]) upd(d[v]^edg[i].v^d[x]);
else d[v]=d[x]^edg[i].v,DFS(v);
}
}
int n,m;
int main () {
ios::sync_with_stdio(0);
cin>>n>>m;
for(int i=1;i<=m;++i) {
ll x,y,z;
cin>>x>>y>>z;
add(x,y,z),add(y,x,z);
}
DFS(1);
ll ans=d[n];
for(int i=60;~i;--i)
if((ans^b[i])>ans) ans^=b[i];
cout<<ans<<endl;
return 0;
}
算法分析:线性基,图形结构,DFS,bitset。
参考例四的思路,只有环上边能对答案做出贡献。因此通过 DFS 找返祖边搜环,扔进线性基里去,找到最大值即可。
注意到含有删除操作。因此把操作离线掉,之前介绍的带删除线性基派上了用场。
另外一个需要注意的点是,经济影响因子的二进制最大长度为
据说有在线做法?srds 笔者太菜了不会。(悲
#include <bitset>
#include <cstdio>
#include <iostream>
using namespace std;
const int N=1010;
typedef bitset<N> BT;
inline void in(BT &x) {
string s;
cin>>s;
BT tmp(s);
x=tmp;
}
inline void out(BT x) {
bool flag=0;
for(int i=999;i>=0;--i) {
if(x[i]||flag) putchar('0'+x[i]);
if(x[i]) flag=1;
}
if(!flag) putchar('0');
putchar('\n');
}
int tot,hd[N];
struct node {
int next,to;
BT v;
} edg[N];
inline void add(int fr,int to,BT v) {
edg[++tot].to=to;
edg[tot].v=v;
edg[tot].next=hd[fr];
hd[fr]=tot;
}
BT p[N];
int t[N];
void upd(BT x,int time) {
for(int i=1000;i>=0;--i)
if(x[i]) {
if(t[i]<time) swap(time,t[i]),swap(x,p[i]);
if(time==0) break;
x^=p[i];
}
}
void getmx(int time) {
BT ans;
for(int i=1000;i>=0;--i)
if(time<t[i]&&!ans[i]) ans^=p[i];
out(ans);
}
BT dis[N];
bool vis[N];
void DFS(int x) {
vis[x]=1;
for(int i=hd[x];i;i=edg[i].next) {
int v=edg[i].to;
if(vis[v]) upd(dis[v]^edg[i].v^dis[x],0x3f3f3f3f);
else dis[v]=dis[x]^edg[i].v,DFS(v);
}
}
int n,m,q,qcnt,ed[N],op[N],del[N];
BT val[N];
pair<int,int>New[N];
int main () {
ios::sync_with_stdio(0);
cin>>n>>m>>q;
int num=q+1;
for(int i=1;i<=m;++i) {
int u,v;
BT w;
cin>>u>>v,in(w);
add(u,v,w),add(v,u,w);
}
DFS(1);
for(int i=1;i<=q;++i) {
string s;
cin>>s;
int x,y;
BT z;
if(s=="Add") {
cin>>x>>y,in(z);
op[i]=++qcnt;
val[qcnt]=(z^dis[x]^dis[y]);
New[qcnt]=make_pair(x,y);
del[qcnt]=qcnt;
}
else if(s=="Change") {
cin>>x,in(z);
ed[del[x]]=i;
op[i]=--num;
val[num]=(z^dis[New[x].first]^dis[New[x].second]);
del[x]=num;
}
else if(s=="Cancel") {
cin>>x;
ed[del[x]]=i;
}
}
for(int i=1;i<=q;++i)
if(!ed[i]) ed[i]=0x3f3f3f3f;
getmx(0);
for(int i=1;i<=q;++i) {
if(op[i]) upd(val[op[i]],ed[op[i]]);
getmx(i);
}
return 0;
}
限于篇幅,恕不赘述。
笔者找到了一个题单,里面的习题还是挺全的,就放在这里了。
参考资料:
线性基详解
线性基求交与求并
男默女泪!线性基求交全网最通俗解释
[WC2011] 最大XOR和路径 题解
[HAOI2017] 八纵八横 题解
另外,感谢魏老师对本文的审核,并提出了意见。
由于笔者很菜,所以本文可能有一些不妥之处,请各路神犇看到后在评论区加以指正。
就此搁笔。希望你在阅读本文以后对线性基有所了解。