线性基小结

线性基

概念

在线性代数中,对于向量组 \(a_1,a_2,...,a_n\) ,我们把其张成空间的一组线性无关的基称为该向量组的线性基。
\(OI\) 中,对于一个二进制集合 \(S={x_1,x_2,...,x_n}\) ,得到另一个二进制集合 \(S'={y_1,y_2,...,y_m}\) ,使得 \(S\) 的任意一个子集的异或和,都能与 \(S'\) 中的一个子集的异或和相等,且 \(y_i\) 不能被 \(S'\) 中的其它元素异或和出来,称 \(S'\)\(S\) 的线性基。
利用线性基可以解决一些与异或有关的问题。

构造

根据异或的性质,我们可以设 \(p_i\) 表示 \(S'\) 中最高位为 \(2^i\) 的,可以证明,因为 \(S'\) 中的元素不能被其他元素给异或出来,所以每个 \(p_i\) 至多有一个。
轮流插入 \(x_i\) ,每次将一个数插入线性基,从最高位开始枚举,设其最高位为 \(k\) ,若:

  • \(p_k=0\)
    \(p_k=x_i\) ,退出。
  • \(p_k>0\)
    \(x_i \oplus =p_k\) ,继续查找下一个最高位。

因为 \(x_i \oplus p_k\) 时,\(x_i\) 的最高位与 \(p_k\) 异或后会消除,所以最高位就会变。
这样 \(x_i\) 就可以被插入时经过的 \(p_{k_1},p_{k_2},...\) 给异或得出,\(x_{i_1} \oplus ...\) 也可以通过 \(p_{q_1} \oplus ...\) 得出。

Code

void dijah(long long h)
{
	for(int i=62;i>=0;i--)
	{
		if(!(h&(1<<i))continue;
		if(!p[i])p[i]=h,s--;
		h^=p[i];
	}
	return;
}

查询

经典问题就是查询最大异或和。
因为最大就是让高位尽量大,直接从高位往低位枚举,每次看是异或好还是不异或好。

Code

for(int i=62;i>=0;i--)s=max(s,s^p[i]);

题目

【Luogu P3812】 【模板】线性基

题目描述

给出 \(n\) 个数,求最大异或和,$n \le 50,a_i \le 2^{50} $

解题思路

线性基模板。

Code

#include<bits/stdc++.h>
using namespace std;
long long n,a[65],l[65];
void dijah(long long x)
{
	for(int i=55;i>=0;i--)
	{
		if((!(x&l[i])))continue;
		if(!a[i])a[i]=x;
		x^=a[i];
	}
	return;
}
long long gaia()
{
	long long h=0;
	for(int i=55;i>=0;i--)h=max(h^a[i],h);
	return h;
}
int main()
{
	long long x;
	scanf("%lld",&n);
	l[0]=1;
	for(int i=1;i<=55;i++)l[i]=l[i-1]*2;
	for(int i=1;i<=n;i++)scanf("%lld",&x),dijah(x);
	printf("%lld",gaia());
  return 0;
}

【Luogu P4570】 元素

题目描述

给出 \(n\) 个物品,每个物品有 \(x_i,y_i\) ,选出一个子集满足 \(x_{i_1}\oplus x_{i_2} \oplus ... \oplus x_{i_m} >0\) ,使得 \(y_i\) 的和最大,\(1 \le n \le 1000,1 \le x_i \le 10^{18}\)

解题思路

因为 \(p_i\) 每一位只能插入一个,所以我们可以贪心,直接按 \(y_i\) 从大到小排序,每一次尝试加入线性基,如果成功加进去,\(ans+=y\)
答案就是 \(ans\)

Code

#include<bits/stdc++.h>
using namespace std;
struct datay
{
	long long x,y;
}a[100005];
long long n,d[100005],l[10005],s;
bool cmp(datay q,datay w)
{
	return q.y>w.y;
}
void dijah(datay x)
{
	for(int i=62;i>=0;i--)
	{
		if(!(x.x&l[i]))continue;
		if(!d[i])d[i]=x.x,s+=x.y;
		x.x^=d[i];
	}
	return;
}
int main()
{
	scanf("%lld",&n);
	l[0]=1;
	for(int i=1;i<=62;i++)l[i]=l[i-1]*2;
	for(int i=1;i<=n;i++)scanf("%lld%lld",&a[i].x,&a[i].y);
	sort(a+1,a+n+1,cmp);
	for(int i=1;i<=n;i++)dijah(a[i]);
	cout<<s;

  return 0;
}

【Luogu P4151】 最大XOR和路径

题目描述

给出一个 \(n\)\(m\) 边的无向图,边有边权,求 \(1\)\(n\) 的一条路径,使得该条路径经过的边权的异或和最大,\(n \le 50000,m \le 100000\)

解题思路

观察题目,我们可以暴力从 \(1\) 遍历到 \(n\) ,但这样会超时,我们考虑优化。
我们可以很快地求出一条从 \(1\)\(n\) 的路径,若我们想将其变成另一条路径,我们会发现,我们只要异或上一个环的异或和即可转换。
若该还不在路径上,我们也会发现,去到该环的路径会被异或两次,也只相当于异或上环的异或和。
综上,一条路径转换成其他路径只是异或上若干个环的异或和,可以用线性基来存储环的异或和,再求换的最大值。

Code

#include<bits/stdc++.h>
using namespace std;
long long n,m,d[50005],l[105],b[105],s;
bool v[50005],v1[50005]; 
vector<long long> a[50005],t[50005];
void dijah(long long x)
{
	for(int i=62;i>=0;i--)
	{
		if(!(x&l[i]))continue;
		if(!b[i])b[i]=x;
		x^=b[i];
	}
	return;
}
void dfs1(long long x,long long y)
{
	if(v[x])
	{
		dijah(y^d[x]);
		return;
	}
	v[x]=true,d[x]=y;
	for(int i=0;i<a[x].size();i++)
	{
		if(v1[a[x][i]])continue;
		dfs1(a[x][i],y^t[x][i]);
	}
	v[x]=false,v1[x]=true;
	return;
} 
void dfs2(long long x,long long y)
{
	if(x==n)
	{
		s=y;
		return;
	}
	v[x]=true;
	for(int i=0;i<a[x].size();i++)
	{
		if(v[a[x][i]])continue;
		dfs2(a[x][i],y^t[x][i]); 
	}
	return;
}
int main()
{
	long long x,y,z;
	scanf("%lld%lld",&n,&m);
	l[0]=1;
	for(int i=1;i<=62;i++)l[i]=l[i-1]*2;
	for(int i=1;i<=m;i++)
	{
		scanf("%lld%lld%lld",&x,&y,&z);
		a[x].push_back(y),a[y].push_back(x),t[x].push_back(z),t[y].push_back(z);
	}
	dfs1(1,0),dfs2(1,0);
	for(int i=62;i>=0;i--)s=max(s,s^b[i]);
	printf("%lld",s);
  return 0;
}

【CF724G】 Xor-matic Number of the Graph

题目描述

给一个 \(n\)\(m\) 边的无向图,边有边权,设 \(f_{i,j}\) 为点 \(i\) 到另一个点 \(j\) 若干条路径互不相同的异或和的和,求 \(\sum_{i=1}^n \sum_{j>i}^n f_{i,j}\)\(n \le 100000,m \le 200000\)

解题思路

考虑上一题的做法,一个点到另一个点的所有路径异或和可以看做一条路径异或上环得到的,同时,一个点 \(u\) 到另一个点 \(v\) 的路径可以表示为 \(1\)\(u\) 的路径的异或和 \(d_u\) 异或上到 \(v\) 的路径的异或和 \(d_v\) ,但这道题有很多条路径,而且要求和,直接枚举会超时。
考虑逐位分析,设线性基中有 \(m\) 个值,对于第 \(i\) 位,设 \(d\) 中有 \(x\) 个数 \(i\) 位为 \(0\) ,有 \(y\) 个数 \(i\) 位为 \(0\) ,那么我们可以分两类讨论:

  • 线性基 \(p\) 中有值第 \(i\) 位为 \(1\)
    那么 \(d\) 可以任选,只要线性基选到的集合保证与 \(d_u \oplus d_v\) 异或后为 \(1\) 即可,根据组合数基本定理,为 \(2_{m-1}\) ,那么有 \(C_{n}^{2} \times 2^{m-1}\) 条路径。
  • 线性基 \(p\) 中无值第 \(i\) 位为 \(1\)
    那么 \(d\) 选的需满足异或起来第 \(i\) 位为 \(1\) ,但线性基里面可以任选,那么有 \(x \times y \times 2^m\) 条路径。

累加即可,注意题目未保证联通,要多次搜索。

Code

#include<bits/stdc++.h>
using namespace std;
const long long mod=1e9+7;
long long n,m,d[100005],l[65],b[65],f1[65],f2[65],d1[65],d2[65],p[100005],s,qw,num;
bool v[100005],v1[100005];
vector<long long> a[100005],t[100005];
long long poww(long long x,long long y)
{
	if(y<0)return 0;
	long long h=1;
	while(y)
	{
		if(y&1)h=(h*x)%mod;
		x=(x*x)%mod,y>>=1;
	} 
	return h;
}
void dijah(long long x)
{
	for(int i=62;i>=0;i--)
	{
		if(!(x&l[i]))continue;
		if(b[i])
		{
			x^=b[i];
			continue;
		}
		b[i]=x,qw++;
		for(int j=62;j>=0;j--)if(x&l[j])d1[j]++;
		break;
	}
	return;
}
void gaia(long long x)
{
	for(int i=0;i<=62;i++)if(x&l[i])f1[i]++;
	return;
}
void dfs1(long long x,long long y)
{
	if(v1[x])return;
	if(v[x])
	{
		dijah(y^d[x]);
		return;
	}
	num++,d[x]=y,v[x]=true,gaia(y);
	for(int i=0;i<a[x].size();i++)dfs1(a[x][i],y^t[x][i]);
	v[x]=false,v1[x]=true;
	return;
}
long long C(long long x,long long y)
{
	if(x<y)return 0;
	return (p[x]*poww(p[x-y],mod-2)%mod)*poww(p[y],mod-2)%mod;
}
int main()
{
	long long x,y,z;
	scanf("%lld%lld",&n,&m);
	l[0]=1,p[0]=1;
	for(int i=1;i<=62;i++)l[i]=l[i-1]*2;
	for(int i=1;i<=100000;i++)p[i]=(p[i-1]*i)%mod;
	for(int i=1;i<=m;i++)
	{
		scanf("%lld%lld%lld",&x,&y,&z);
		a[x].push_back(y),a[y].push_back(x),t[x].push_back(z),t[y].push_back(z);
	}
	for(int i=1;i<=n;i++)
	{
		if(!v1[i])
		{
			memset(b,0,sizeof(b)),memset(f1,0,sizeof(f1)),memset(d1,0,sizeof(d1));
			qw=num=0;
			dfs1(i,0);
			for(int j=0;j<=62;j++)s=(s+((d1[j]?(C(num,2)):0)*poww(2,qw-1)%mod+(d1[j]?0:f1[j]*(num-f1[j]))*poww(2,qw)%mod)*poww(2,j))%mod;
		}
	}
	cout<<s;

  return 0;
}

【Luogu P3292】 幸运数字

题目描述

给出一个 \(n\) 点的树,点有点权,\(m\) 次询问,每次求点 \(x\) 到点 \(y\) 经过的点中任选若干个的最大异或和。

解题思路

看到异或和就可以联想到线性基,但是不能很快地求 \(x\)\(y\) 的线性基,考虑拆开来求。
\(LCA(x,y)=z\) ,那么我们只需求出 \(x\)\(z\) 的线性基和 \(y\)\(z\) 的线性基然后在合并在一起即可。
考虑如何求一个节点往上一段的线性基。
我们做每个节点的线性基时从深度深的节点往上做,这样做的话,深度大的节点加入线性基时就不会被深度低的节点影像,查询时可以直接忽略深度小于 \(z\) 的节点。
但是直接做的时间复杂度是 \(O(n^2logV)\) ,考虑每次做从父亲节点转移。
每次加入时,找到重复的 \(p_i\) 时不是先异或,而是先比一下谁的深度大,如果 \(p_i\) 的深度小的话,可以强行把 \(p_i\) 挤出来,自己存 \(p_i\) 让挤出来的数继续查询。
这样做时间复杂度为 \(O(nlogV)\)

Code

#include<bits/stdc++.h>
using namespace std;
struct datay
{
	long long v,x;
}b[20005][65],d[65];
long long n,m,v[20005],l[65],deep[20005],f[20005][21];
vector<long long> a[20005];
void dfs1(long long x,long long y)
{
	for(int i=0;i<=62;i++)b[x][i]=b[y][i];
	datay q;q.v=v[x],q.x=x,deep[x]=deep[y]+1,f[x][0]=y;
	for(int i=62;i>=0;i--)
	{
		if(!(q.v&l[i]))continue;
		if(b[x][i].v)
		{
			if(b[x][i].x<q.x)swap(b[x][i],q);
			q.v^=b[x][i].v;
			continue;
		}
		b[x][i]=q;
		break;
	}
	for(int i=0;i<a[x].size();i++)
	{
		if(a[x][i]==y)continue;
		dfs1(a[x][i],x);
	}
	return;
}
long long LCA(long long x,long long y)
{
	if(deep[x]<deep[y])swap(x,y);
	for(int i=20;i>=0;i--)x=(deep[f[x][i]]>=deep[y]?f[x][i]:x);
	if(x==y)return x;
	for(int i=20;i>=0;i--)x=(f[x][i]!=f[y][i]?f[x][i]:x),y=(deep[x]!=deep[y]?f[y][i]:y);
	return f[x][0];
}
int main()
{
	long long x,y,z,s=0,q;
	scanf("%lld%lld",&n,&m);
	l[0]=1;
	for(int i=1;i<=62;i++)l[i]=l[i-1]*2;
	for(int i=1;i<=n;i++)scanf("%lld",&v[i]);
	for(int i=1;i<n;i++)
	{
		scanf("%lld%lld",&x,&y);
		a[x].push_back(y),a[y].push_back(x); 
	}
	dfs1(1,0);
	for(int i=1;i<=20;i++)
	{
		for(int j=1;j<=n;j++)f[j][i]=f[f[j][i-1]][i-1];
	}
	for(int i=1;i<=m;i++)
	{
		scanf("%lld%lld",&x,&y);
		z=LCA(x,y),s=0;
		memset(d,0,sizeof(d));
		for(int j=0;j<=62;j++)if(deep[b[x][j].x]>=deep[z])d[j]=b[x][j];
		for(int j=62;j>=0;j--)
		{
			if(deep[b[y][j].x]<deep[z]||(!b[y][j].v))continue;
			q=b[y][j].v;
			for(int u=62;u>=0;u--)
			{
				if(!(q&l[u]))continue;
				if(d[u].v&&deep[d[u].x]>=deep[z])
				{
					q^=d[u].v;
					continue;
				}
				d[u].v=q,d[u].x=b[y][j].x;
				break;	
			}
		}
		for(int j=62;j>=0;j--)if(deep[d[j].x]>=deep[z])s=max(s,s^d[j].v);
		printf("%lld\n",s);
	}
  return 0;
}

【Luogu P5556】 圣剑护符

题目描述

给出一个 \(n\) 点的树,点有点权,每次有两个操作,将一段路径上的全部异或上一个值或查询一段路径上的所有点的集合是否能选出两个异或和相同的子集。

解题思路

首先,因为数 \(< 10^9\) ,根据线性基的定义可知,长度大于 \(31\) 的路径是必有重的。
所以我们只需解决长度 \(<31\) 的路径,一个一个加即可。
但是要维护树上操作,可以套树剖加树状数组。

Code

#include<bits/stdc++.h>
using namespace std;
long long n,m,v[100005],f[100005],son[100005],fa[100005],deep[100005],size[100005],dfn[100005],top[100005],num,d[35],l[35],out[100005];
vector<long long> a[100005];
bool qwe;
long long lowbit(long long x)
{
	return x&(-x);
}
void dijah(long long x,long long y)
{
	if(x==0)return;
	for(int i=x;i<=n+1;i+=lowbit(i))f[i]^=y;
	return;
}
long long gaia(long long x)
{
	long long h=0;
	while(x)h^=f[x],x-=lowbit(x);
	return h;
}
void dfs1(long long x,long long y)
{
	size[x]=1,deep[x]=deep[y]+1,fa[x]=y;
	for(int i=0;i<a[x].size();i++)
	{
		if(a[x][i]==y)continue;
		dfs1(a[x][i],x),size[x]+=size[a[x][i]];
		if(size[son[x]]<size[a[x][i]])son[x]=a[x][i];
	}
	return;
}
void dfs2(long long x,long long y)
{
	dfn[x]=++num;
	if(son[x])top[son[x]]=top[x],dfs2(son[x],x);
	for(int i=0;i<a[x].size();i++)
	{
		if(a[x][i]==y||a[x][i]==son[x])continue;
		top[a[x][i]]=a[x][i],dfs2(a[x][i],x);
	}
	out[x]=num;
	return;
}
void update(long long x,long long y,long long z)
{
	while(top[x]!=top[y])
	{
		if(deep[top[x]]<deep[top[y]])swap(x,y);
		dijah(dfn[x]+1,z),dijah(dfn[top[x]],z),x=fa[top[x]];
	}
	if(deep[x]>deep[y])swap(x,y);
	dijah(dfn[y]+1,z),dijah(dfn[x],z);
	return;
}
long long query(long long x,long long y)
{
	while(top[x]!=top[y])
	{
		if(deep[top[x]]<deep[top[y]])swap(x,y);
		x=fa[top[x]];
	}
	return (deep[x]<deep[y]?x:y);
}
void add(long long x)
{
	for(int i=32;i>=0;i--)
	{
		if(!(x&l[i]))continue;
		if(d[i])
		{
		    x^=d[i];
		    continue;
		}
		d[i]=x;
		break;
	}
	if(x==0)qwe=true;
	return;
}
int main()
{
	string op;
	long long x,y,z;
	l[0]=1;
	for(int i=1;i<=31;i++)l[i]=l[i-1]*2;
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=n;i++)scanf("%lld",&v[i]);
	for(int i=1;i<n;i++)scanf("%lld%lld",&x,&y),a[x].push_back(y),a[y].push_back(x);
	dfs1(1,0),top[1]=1,dfs2(1,0);
	for(int i=1;i<=n;i++)dijah(dfn[i],v[i]),dijah(dfn[i]+1,v[i]);
	for(int i=1;i<=m;i++)
	{
		cin>>op;
		if(op=="Update")scanf("%lld%lld%lld",&x,&y,&z),update(x,y,z);
		else
		{
			scanf("%lld%lld",&x,&y);
			z=query(x,y),qwe=false;
			if(deep[x]+deep[y]-2*deep[z]>=33)printf("YES\n");
			else
			{
				memset(d,0,sizeof(d));
				while(x!=z)add(gaia(dfn[x])),x=fa[x];
				while(y!=z)add(gaia(dfn[y])),y=fa[y];
				add(gaia(dfn[z]));
				if(!qwe)printf("NO\n");
				else printf("YES\n");
			}
		}
	}
  return 0;
}

【Luogu P5607】 无力回天 NOI2017

题目描述

给一个序列,维护两个操作:将一个区间的数全异或上 \(v\) ,或求与 \(v\) 的最大异或和。

解题思路

看到最大异或和考虑线性基,但是区间带线性基是很难做的,转化成异或操作。
设原数组为 \(a_i\) ,差分数组为 \(b_i\) ,查询时要加入线性基的数分别为 \(a_l,a_l \oplus b_{l+1},a_l \oplus b_{l+1} \oplus b_{l+2},...\) ,观察可发现,由于 \(a_l\) 已插入,插入 \(b_{l+1} \oplus a_l\) 相当于插入 \(b_{l+1}\) ,所以查询就是查 \(a_l,b_{l+1},b_{l+2},...\) 组成的线性基。
那就是维护两个操作:修改一个位置,或查询一段区间组成的线性基。
可以用线段树维护,每个节点存储一段区间的线性基,合并时间复杂度 $O(logV) $ ,总时间复杂度 \(O(nlog^2V)\)

Code

#include<bits/stdc++.h>
using namespace std;
struct datay
{
	long long v[35];
}f[200005],qwe;
long long n,m,l[35],f1[200005];
long long lowbit(long long x)
{
	return x&(-x);
}
void add(long long x,long long y)
{
	for(int i=x;i<=n+1;i+=lowbit(i))f1[i]^=y;
	return;
}
long long ask(long long x)
{
	long long h=0;
	while(x)h^=f1[x],x-=lowbit(x);
	return h;
}
void galaxy(long long x,long long v)
{
	for(int i=0;i<=32;i++)if(f[x].v[i])v^=f[x].v[i],f[x].v[i]=0;
	for(int i=32;i>=0;i--)
	{
		if((v&l[i]))
		{
			f[x].v[i]=v;
			break;
		}
	}
	return;
}
datay merge(datay x,datay w)
{
	for(int i=32;i>=0;i--)
	{
		if(!w.v[i])continue;
		for(int j=i;j>=0;j--)
		{
			if(!(w.v[i]&l[j]))continue;
			if(!x.v[j])x.v[j]=w.v[i];
			w.v[i]^=x.v[j];
		}
	}
	return x;
}
void dijah(long long x,long long l,long long r,long long k,long long v)
{
	if(l==r)
	{
		galaxy(x,v);
		return;
	}
	long long mid=(l+r)>>1;
	if(k<=mid)dijah(x<<1,l,mid,k,v);
	else dijah((x<<1)|1,mid+1,r,k,v);
	f[x]=merge(f[x<<1],f[(x<<1)|1]); 
	return;
}
datay gaia(long long x,long long l,long long r,long long ql,long long qr)
{
	if(ql<=l&&r<=qr)return f[x];
	long long lc=(x<<1),rc=(x<<1)|1,mid=(l+r)>>1;
	datay h;h=qwe;
	if(ql<=mid)h=gaia(lc,l,mid,ql,qr);
	if(qr>mid)h=merge(h,gaia(rc,mid+1,r,ql,qr));
	return h;
}
int main()
{
	long long x,y,z,p;
	scanf("%lld%lld",&n,&m);
	l[0]=1,y=0,qwe.v[0]=0;
	for(int i=1;i<=32;i++)l[i]=l[i-1]*2,qwe.v[i]=0;
	for(int i=1;i<=n;i++)scanf("%lld",&x),z=x,x^=y,dijah(1,1,n+1,i,x),add(i,x),y=z;
	for(int i=1;i<=m;i++)
	{
		scanf("%lld%lld%lld%lld",&p,&x,&y,&z);
		if(p==1)dijah(1,1,n+1,x,z),dijah(1,1,n+1,y+1,z),add(x,z),add(y+1,z);
		else
		{
			if(x!=y)
			{
				datay h=gaia(1,1,n+1,x+1,y);y=ask(x);
				for(int j=32;j>=0;j--)
				{
					if(!(y&l[j]))continue;
					if(!h.v[j])h.v[j]=y;
					y^=h.v[j];
				}
				for(int j=32;j>=0;j--)z=max(z,z^h.v[j]);
				printf("%lld\n",z);
			} 
			else y=ask(x),printf("%lld\n",max(z,y^z));
		}
	}
  return 0;
}

【CF895C】 Square Subsets

题目描述

给出一个 \(n\) 个数,求有多少个子序列使得乘积为完全平方数。

解题思路

容易发现,对于一个数,其每个质因数的指数可分为奇偶两种状态,看做二进制的 \(01\) ,那么每个质数占一位,每个数可以表示为一个二进制数,相乘之后的结果可以看做代表的二进制数异或后的结果。
那就是找多少个子集为 \(0\)
线性基模板,只需统计有多少个数被线性基中的数异或到 \(0\) 即可。

Code

#include<bits/stdc++.h>
using namespace std;
const long long mod=1e9+7;
long long n,d[65],l[65],x,s=1,prime[65]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67};
void dijah(long long x)
{
	long long h=0;
	for(int i=18;i>=0;i--)
	{
	    while(x%prime[i]==0)h^=l[i],x/=prime[i];
	}
	for(int i=18;i>=0;i--)
	{
		if(!(h&l[i]))continue;
		if(!d[i])d[i]=h,s=(s*500000004)%mod;
		h^=d[i];
	}
	s=(s*2)%mod;
	return;
}
int main()
{
	l[0]=1;
	for(int i=1;i<=62;i++)l[i]=l[i-1]*2;
	scanf("%lld",&n);
	for(int i=1;i<=n;i++)scanf("%lld",&x),dijah(x);
	cout<<s-1<<'\n';

  return 0;
}

【Luogu P4869】 albus就是要第一个出场

题目描述

给出一个集合 \(S\) ,求 \(k\)\(S\) 的所有子集的异或和排序后第几个出现。

解题思路

线性基模板题。
求出 \(S\) 的线性基,求 \(k\) 在线性基的若干异或和中第几个出现。
由于 \(S\) 的所有子集异或和每个数会重复,我们只需统计加入线性基时有多少个数被异或到 \(0\) 设为 \(x\),那么每个数就会重复 \(2^x\) 次,只需乘上 \(2^x\) 即可。

Code

#include<bits/stdc++.h>
using namespace std;
const long long mod=10086;
long long n,x,m,d[65],l[65],s=0;
void dijah(long long h)
{
	for(int i=62;i>=0;i--)
	{
		if(!(h&l[i]))continue;
		if(!d[i])d[i]=h,s--;
		h^=d[i];
	}
	s++;
	return;
}
int main()
{
	long long q=0,w=1;l[0]=1;
	for(int i=1;i<=62;i++)l[i]=l[i-1]*2;
	scanf("%lld",&n);
	for(int i=1;i<=n;i++)scanf("%lld",&x),dijah(x);
	scanf("%lld",&m);
	for(int i=0;i<=62;i++)
	{
		if((m&l[i])&&d[i])q+=w;
		if(d[i])w*=2;
	}
	for(int i=1;i<=s;i++)q=(2*q)%mod;
	cout<<(q+1)%mod;
  return 0;
}

【CF1163E】 Magical Permutation

题目描述

给出一个集合 \(S\) ,求最大的 \(x\) 使得存在一个长度为 \(2^x\) 的排列满足相邻的项的异或和都为 \(S\) 里面的数,输出最大 \(x\) 以及构造排列。

解题思路

由于排列中有 \(0\) ,我们可以得知排列中的每个数都为 \(S\) 的某个子集的异或和,也就是 \(S\) 的线性基的子集的异或和。
我们可以从小到大枚举 \(x\) ,看用 \(S\) 中小于 \(2^x\) 的数来构成线性基,\(0\)~\(x-1\) 是否能被填满。
这样就可以找出最大的 \(x\) ,考虑构造。
我们可以求出线性基中的数分别是那些数得来的,所以我们只用这些数来做相邻两数的异或和。
参考格雷码的方法,设我们做到第 \(i\) 位,设 \(i\) 二进制最右的 \(1\) 在第 \(j\) 位,那么我们异或上第 \(j\) 个数。
这种类似分治,可以保证一个长度为二的幂次的区间右边只是比左边多异或上一个数。

Code

#include<bits/stdc++.h>
using namespace std;
long long n,a[400005],l[35],v[400005],d[35],f[35],b[400005];
int main()
{
	long long s=0,q=1,w;
	scanf("%lld",&n);
	l[0]=1;
	for(int i=1;i<=25;i++)l[i]=l[i-1]*2;
	for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
	sort(a+1,a+n+1),a[n+1]=1e9+5;
	memset(v,-1,sizeof(v));
	for(int i=1;i<=25;i++)
	{
		while(q<=n&&l[i]-1>=a[q])
		{
			w=a[q];
			for(int j=25;j>=0;j--)
			{
				if(!(a[q]&l[j]))continue;
				if(d[j]==0)v[q]=j,d[j]=a[q];
				a[q]^=d[j];
			}
			a[q]=w,q++;
		}
		q=0;
		for(int j=0;j<i;j++)if(!d[j])q=1;
		if(!q)s=i;
	}
	for(int i=1;i<=n;i++)if(v[i]!=-1)f[v[i]]=a[i];
	printf("%lld\n",s);
	n=l[s],s=0;
	for(int i=1;i<=n;i++)
	{
		q=i,w=0;
		for(int j=0;j<25;j++)if(l[j]&q)w=j,q=0;
		printf("%lld ",s),s^=f[w];
	}
  return 0;
}
posted @ 2024-03-13 16:37  dijah  阅读(10)  评论(0编辑  收藏  举报