线性基学习笔记
模板
void insert(ll *a,ll x){//插入新元素
for(int i=62;i>=0;i--){
if((x>>i)&1ll){
if(!a[i]){
a[i]=x;
return;
}
else{
x^=a[i];
}
}
}
}
ll getMax(ll *a){//得到线性基中的最大异或和
ll ans=0;
for(int i=62;i>=0;i--){
if((ans^a[i])>ans){
ans^=a[i];
}
}
return ans;
}
ll merge(ll *a,ll *b){//将b合并到a
for(int i=62;i>=0;i--){
if(b[i])insert(a,b[i]);
}
}
ll getRank(ll *a,ll x){//返回x是线性基生成空间中第k个数(从小到大)
ll rank=1,temp=1;
for(int i=0;i<=62;i++){
if(xxj[i]){
if((x>>i)&1ll){
rank+=temp;//可能会爆ll
}
temp<<=1;
}
}
return rank;
}
例题
P3265 [JLOI2015]装备购买 (实数域线性基)
题意:
给定n个向量和每个向量的价值,问最多可以选择多少个向量使得它们都是线性无关的,然后求出选择最多向量的最小代价
解法:
考虑建立实数域线性基,每次加入新向量从高到低判断该分量是否为0,若不为0,若该位已经存在基底,就将新向量进行一次消元,使得该分量为0;若不存在基底,则将当前向量作为该位的基底。
贪心地优先选择代价小的向量,依次构建线性基即可,实数域线性基本质上是高斯消元法。
代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e3+5;
const double eps=1e-3;
int sgn(double x) {
if(fabs(x) < eps)
return 0;
if(x < 0)
return -1;
return 1;
}
struct Node{
double a[maxn];
int w;
}z[maxn];
int place[maxn];//place[i]=j表示存第i个基底为第j个向量
bool cmp(Node a,Node b){
return a.w<b.w;
}
int main () {
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%lf",&z[i].a[j]);
}
}
for(int i=1;i<=n;i++){
scanf("%d",&z[i].w);
}
sort(z+1,z+1+n,cmp);
int ans1=0,ans2=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(sgn(z[i].a[j])==0){
continue;
}
if(!place[j]){
place[j]=i;
ans1++;
ans2+=z[i].w;
break;
}
else{//将z[i].a[j]减为0
double temp=z[i].a[j]/z[place[j]].a[j];
for(int k=1;k<=m;k++){
z[i].a[k]-=temp*z[place[j]].a[k];
}
}
}
}
printf("%d %d\n",ans1,ans2);
}
P3292 [SCOI2016]幸运数字(树上倍增套线性基)
题意:
给定一棵树大小为n,树上的点有点权wi,q次询问,每次询问给出u,v,问u,v路径上的点权集合,如何选择子集使子集异或和最大。
思路:
异或和最大那肯定想到用线性基,树上的路径想到树上倍增,如何将两者结合起来?
对每个点的每阶祖先增加一个数组用来存该点到祖先之间所有点的线性基,即\(xxj[maxn][20][65]\)。在询问时先求出两点的lca,然后对路径上的线性基进行合并即可。因为线性基重复插入元素是没关系的,所以这里的合并可以用ST表中的O(1)查询的思想,在u->lca路径上合并两个有重复区域的区间,同理在v->lca路径上也是这样,就可以得到整条路径的线性基。
预处理时间复杂度为\(O(n*log^3n)\),查询时间复杂度为\(O(m*log^2n)\)
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e4 + 5;
ll w[maxn],lg[maxn];
//-----------线性基--------------
void insert(ll *a,ll x){
for(int i=60;i>=0;i--){
if((x>>i)&1ll){
if(!a[i]){
a[i]=x;
return;
}
else{
x^=a[i];
}
}
}
}
ll getMax(ll *a){
ll ans=0;
for(int i=60;i>=0;i--){
if((ans^a[i])>ans){
ans^=a[i];
}
}
return ans;
}
ll merge(ll *a,ll *b){//将b合并到a
for(int i=60;i>=0;i--){
if(b[i])insert(a,b[i]);
}
}
//-------------------------------
//----------树上倍增-------------
const int maxm = 4e4 + 5;
struct edge {
int next, v;
}E[maxm];
int head[maxn];
int tot = 0;
void addedge(int u, int v) {
E[++tot].next = head[u];
E[tot].v = v;
head[u] = tot;
}
int dep[maxn];
int pa[maxn][20];
ll xxj[maxn][20][65];
void dfs(int u, int father) {
dep[u] = dep[father]+1;
pa[u][0] = father;
insert(xxj[u][0],w[u]);
for(int i=1;i<=lg[dep[u]];i++){
pa[u][i]=pa[pa[u][i-1]][i-1];
merge(xxj[u][i],xxj[u][i-1]);
merge(xxj[u][i],xxj[pa[u][i-1]][i-1]);
}
for (int i = head[u]; i; i = E[i].next) {
int v = E[i].v;
if (v != father) {
dfs(v, u);
}
}
}
int getLca(int u, int v) {
if (dep[u] < dep[v]) swap(u, v);
for (int j = lg[dep[u]]; j >= 0; j--) {//让u,v到达同一高度
if (dep[u] - (1 << j) >= dep[v]) {
u = pa[u][j];
}
}
if (u == v) return u;
for (int j = lg[dep[u]]; j >= 0; j--) {
if (pa[u][j] != pa[v][j]) {
u = pa[u][j];
v = pa[v][j];
}
}
return pa[u][0];
}
int kth(int u,int k){
for(int i=16;i>=0;i--){
if(k&(1<<i)) u=pa[u][i];
}
return u;
}
ll ans[65];
ll query(int u,int v){
memset(ans,0,sizeof ans);
int lc=getLca(u,v);
int len=lg[dep[u]-dep[lc]];
merge(ans,xxj[lc][0]);
merge(ans,xxj[u][len]);
merge(ans,xxj[kth(u,dep[u]-dep[lc]-(1<<len))][len]);
len=lg[dep[v]-dep[lc]];
merge(ans,xxj[v][len]);
merge(ans,xxj[kth(v,dep[v]-dep[lc]-(1<<len))][len]);
return getMax(ans);
}
//--------------------------------------------
void init(int n){
for(int i=2;i<=n;i++){
lg[i]=lg[i/2]+1;
}
}
int main () {
int n,q;
scanf("%d%d",&n,&q);
init(n);
for(int i=1;i<=n;i++){
scanf("%lld",&w[i]);
}
for(int i=1;i<=n-1;i++){
int u,v;
scanf("%d%d",&u,&v);
addedge(u,v);
addedge(v,u);
}
dfs(1,0);
for(int i=1;i<=q;i++){
int u,v;
scanf("%d%d",&u,&v);
ll ans=query(u,v);
printf("%lld\n",ans);
}
}
P4151最大异或和路径
题意:
给定一张无向图,可能有自环或重边,问1到n的最大异或和路径。
解法:
可以发现,对于一条从1到n的路径,可以在图上绕一个环来增加一个环的异或和。问题就可以转换为,首先找到任意一条1到n的路径,然后将图上任意个环的贡献加入,问最大异或和,这就是一个简单线性基维护了。
P.S.带初值的最大值查询只是要将ans开始置为初值即可,贪心策略不受影响。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn=5e4+5;
const ll maxm=2e5+5;
struct node{
ll v,next;
ll w;
}E[maxm];
ll head[maxn],tot;
void addedge(ll u,ll v,ll w){
E[++tot].v=v;
E[tot].w=w;
E[tot].next=head[u];
head[u]=tot;
}
ll xxj[65];
void insert(ll *a,ll x){//插入新元素
for(ll i=63;i>=0;i--){
if((x>>i)&1ll){
if(!a[i]){
a[i]=x;
return;
}
else{
x^=a[i];
}
}
}
}
ll getMax(ll *a,ll ans){//得到线性基中的最大异或和
for(ll i=63;i>=0;i--){
if((ans^a[i])>ans){
ans^=a[i];
}
}
return ans;
}
ll vis[maxn],xs[maxn];
void dfs(ll u,ll fa,ll temp){
vis[u]=1;
xs[u]=temp;
for(ll i=head[u];i;i=E[i].next){
ll v=E[i].v,w=E[i].w;
if(v==fa)continue;
if(!vis[v]){
dfs(v,u,temp^w);
}
else{
insert(xxj,xs[u]^w^xs[v]);
}
}
}
int main () {
ll n,m;
scanf("%lld%lld",&n,&m);
for(ll i=1;i<=m;i++){
ll u,v;
ll w;
scanf("%lld%lld%lld",&u,&v,&w);
addedge(u,v,w);
addedge(v,u,w);
}
dfs(1,0,0);
ll ans=getMax(xxj,xs[n]);
printf("%lld\n",ans);
}