线性基
线性基
简述:
根据原有数组构造一个新数组(\(log\) 大小),原有数组的任何数都可用新数组中的若干个数异或得到,同时新数组中任意数组合异或出的数一定是原数组若干个数异或得到的数,并且任何原数组若干个数组合起来的异或值一定可以由新数组若干个数异或表示,且只有一种组合方案,即将新数组任意组合异或可不重不漏的得到所有原数组任意数组合出的异或值(如果一个数在原数组中被多次异或出,它在新数组中只会被异或出一次)。
构造方法:
将原数组元素一个一个插入,从大到小遍历其二进制下的每一位,如果此位为一,则看看这个位置的新数组中是否有数,如果没有,则在此位放入新元素,如果有,则将插入元素异或上此处的值,如果异或成 \(0\) ,说明这个数本来就可以用原来的数表示出来,无贡献,因此可结束插入,否则继续遍历直到成功插入或异或成 \(0\)。
一些性质:由这种插入方式可见,第 \(j\) 位的元素在二进制下第 \(j\) 位以上都是 \(0\)。(很实用)
原数组的插入顺序不会影响到新数组的性质,但会影响到新数组中存放的数。
代码:
for(int i=1;i<=n;i++)
	{
		for(int j=60;j>=0;j--)
		{
			if((1ll<<j)&x){
				if(p[j]) x^=p[j];
				else{
					p[j]=x;
					break;
				}
			}
		}
	}
几种简单运用:
1.求可异或出的所有数的最大值:
从高位向低位遍历,到第\(j\)位时,如果此时记录的答案这位是 \(0\),将答案异或上此处的值即可。
2.求出 \(x\) 在可异或出的所有数中排第几:见例题三。
3.求异或出的所有数中的第 \(k\) 小:
先对线性基进行如下处理:
		for(int i=62;i>=0;i--)
		for(int j=i-1;j>=0;j--)
		if((p[i]>>j)&1) p[i]^=p[j];
使高位线性基在线性基有元素的低位上都是零。
接着从高位向低位,假设遍历到线性基第 \(p\) 个元素是第 \(j\) 位,如果 \(k>=2^p\) \(^-\) \(^1\) ,就将答案异或上此处的值,再将 \(k-=(1<<p-1)\),说明已经有 \(2^p\) \(^-\) \(^1\) 是小于答案了,因为之后的值怎么选都不如异或上此位大。有了上面的处理,每次异或都是刚好扩大了\(2^p\) \(^-\) \(^1\) 名,如果没有处理的话,异或后会扩大更多的名次。
这里有道题:K小异或和 ,但不知为啥,没交对。先咕.... 错误原因:在最后找答案计算答案时一定要用异或而不要用加。
例题:
一:P4570 [BJWC2011]元素
题意:
给定若干个元素,每个元素有两个数值,序号和价值,从中选出若干个数,使价值最大,当一个元素的序号可被其他选出的数的序号组合表示出来的话,此元素的价值被抵消。
做法:
先将元素按价值排序,再一个一个插入线性基中,如果成功插入,则加上贡献,如果此位置被占,那它的价值是没有原来的数高的,因此保留原来的数即可。
code:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e3+8;
struct ppp{
	ll x,v;
} a[N];
ll p[N];
bool cmp(ppp a,ppp b)
{
	return a.v>b.v;
}
int main()
{
	int n,ans=0;
	cin>>n;
	for(int i=1;i<=n;i++) scanf("%lld%lld",&a[i].x,&a[i].v);
	sort(a+1,a+1+n,cmp);
	for(int i=1;i<=n;i++)
	{
		for(int j=60;j>=0;j--)
		{
			if((1ll<<j)&a[i].x){
				if(p[j]) a[i].x^=p[j];
				else{
					ans+=a[i].v;
					p[j]=a[i].x;
					break;
				}
			}
		}
	}
	cout<<ans;
}
二:P3857 [TJOI2008]彩灯
题意:
给定若干个数,求出它们相互异或可得到多少种数。
做法:
构建出线性基,因为线性基不重不漏的优美性质,答案为 \(2^x\),\(x\) 为线性基元素个数。
code:
#include<bits/stdc++.h>
using namespace std;
int n,m;
#define ll long long
const int N=55; 
ll p[N],ans=1;
char s[N];
int mod=2008;
int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		scanf("%s",s+1);
		ll x=0;
		for(int j=1;j<=n;j++)
		if(s[j]=='O') x+=(1ll<<j-1);
		for(int j=n-1;j>=0;j--)
		{
			if(x&(1ll<<j))
			{
				if(p[j]) x^=p[j];	
				else {
					p[j]=x;ans<<=1;ans%=mod;
					break;
				}
				if(x==0) break;
			} 	
		}	
	}	
	cout<<ans;
}
*三:P4869 albus就是要第一个出场
题意:
给定若干个数,求把他们任意组合异或得到的所有数中,\(x\) 排第几,注意不去重。
做法:
对于任意一个数,其出现的次数都是 \(2^n\) \(^-\) \(^p\),其中 \(p\) 为线性基元素个数。
证明:任意一个数在线性基中只有一种表示方法,假设现在的数对于线性基有一种表示方法,对于未更新线性基的 \(n-p\) 个数,每个数也都只有一种表示方法,因此每多加一个数,为保证最后异或出的数不变,在线性基中都可以选出唯一一种方案做出调整,及异或起来等于添加进去的数的方案,这样就得到了新的方案,那么添加数一共有 \(2^n\) \(^-\) \(^p\) 种方案,则对于一个数一共就有了 \(2^n\) \(^-\) \(^p\) 种表示方法。
那么我们只需要求出 \(x\) 在线性基构成的数中排第几,之后乘上 \(2^n\) \(^-\) \(^p\) 就行。
这个的求法:从大到小遍历线性基的元素,假设遍历到第 \(k\) 个元素是第 \(j\) 位,如果此时的 \(x\) 第 \(j\) 位是 \(1\),那么因为之后的数第 \(j\) 位都是 \(0\),那么之后的 \(k-1\) 个元素任意组成的 \(2^k\) \(^-\) \(^1\) 个数都比这个数要小让答案加上 \(2^k\) \(^-\) \(^1\) 即可,同时让 \(x\) 异或上此位的线性基。
code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int cnt=1;
int mod=10086;
int p[36];
int n;
signed main()
{
	cin>>n;
	int x;
	for(int i=1;i<=n;i++){
		scanf("%lld",&x);
		for(int j=30;j>=0;j--)
		{
			if(x&(1ll<<j))
			{
				if(!p[j]){
					p[j]=x;
					break;
				}
				x^=p[j];
				if(x==0){
					(cnt<<=1)%=mod;
					break;
				}
			}
		}
	}
	cin>>x;
	int ans=0;
	int pp=1;
	for(int i=0;i<=30;i++)
	{
		if(p[i]){
			if(x&(1ll<<i)) ans|=pp;
			pp<<=1;
		}
	}
	ans%=mod;
	cout<<(ans*cnt+1)%mod;
}
*四:P4151 [WC2011]最大XOR和路径
题意:
每个边有一个权值,求从起点到终点使路径上的权值异或和最大。
做法:
找出所有的环的异或和,加入线性基,最后以最初找到的路径作为初值,求异或出的最大值即可。
每异或一个环,就相当于换了一条路径,任取数异或即可。
code:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=5e4+8;
int n,m;
int fr[N],to[N<<2],nxt[N<<2],too;
ll w[N<<2];
ll p[66];
void add(int x,int y,ll z)
{
	to[++too]=y;
	nxt[too]=fr[x];
	fr[x]=too;
	w[too]=z;
}
void in(ll x)
{
	for(int i=62;i>=0;i--)
	{
		if(x&(1ll<<i)){
			if(!p[i]){
				p[i]=x;
				return;
			}
			x^=p[i];
		}
	}
}
ll del[N],vis[N];
void dfs(int x,ll res)
{
	del[x]=res;
	vis[x]=1;
	for(int i=fr[x];i;i=nxt[i])
	{
		int y=to[i];
		if(!vis[y]) dfs(y,res^w[i]);
		else in(res^w[i]^del[y]);
	}
}
int main()
{
	int x,y;
	ll z;
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%lld",&x,&y,&z);
		add(x,y,z);add(y,x,z);
	}
	dfs(1,0);
	for(int i=62;i>=0;i--)
	{
		if((del[n]^p[i])>del[n])
		del[n]^=p[i];
	}
	cout<<del[n];
}
*五:P3292 [SCOI2016]幸运数字
题意:
给定一棵有点权的树和若干个询问,求一条链上任取若干个数异或的最大值。
做法:
倍增,两个线性基可直接 \(log^2\) 合并,即将一个线性基的每一个元素依次插入另一个线性基中,对于询问,先找到 \(lca\),因为重复插入没关系,因此根据深度利用 \(RMQ\) 的思想合并即可。
code:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=2e4+8;
int n,m;
int f[N][18];
ll px[N][18][66];
int lg[N];
ll a[N];
int fr[N],to[N<<1],nxt[N<<1],too=0;
int dep[N];
void add(int x,int y)
{
	to[++too]=y;
	nxt[too]=fr[x];
	fr[x]=too;
}
void hb(int k,int p,int k1,int p1,int k2,int p2)
{
	if(p!=p1)
	for(int i=60;i>=0;i--)
	px[k][p][i]=px[k1][p1][i];
	for(int i=60;i>=0;i--)
	{
		if(px[k2][p2][i])
		{
			ll x=px[k2][p2][i];
			for(int j=60;j>=0;j--)
			{
				if(x&(1ll<<j)) {
					if(px[k][p][j]) x^=px[k][p][j];
					else{
						px[k][p][j]=x;
						break;
					}
					if(x==0) break;
				}
			}
		}
	}
}
void dfs(int x,int fa)
{
	dep[x]=dep[fa]+1;
	f[x][0]=fa;
	for(int i=60;i>=0;i--)
	{
		if((1ll<<i)&a[x]){
			px[x][0][i]=a[x];
			break;
		}
	}
	for(int i=0;i<=14;i++)
	{
		f[x][i+1]=f[f[x][i]][i];
		if(f[x][i+1]==0) break;
		hb(x,i+1,x,i,f[x][i],i);
	}
	for(int i=fr[x];i;i=nxt[i])
	{
		int y=to[i];
		if(y==fa) continue;
		dfs(y,x);
	}
}
int lca(int x,int y)
{
	if(dep[x]<dep[y]) swap(x,y);
	for(int i=15;i>=0;i--)
	if(dep[f[x][i]]>=dep[y]) x=f[x][i];
	if(x==y) return x;
	for(int i=15;i>=0;i--)
	{
		if(f[x][i]!=f[y][i])
		x=f[x][i],y=f[y][i];	
	}	
	return f[x][0];
}
int kth(int x,int k)
{
	for(int i=15;i>=0;i--){
		if(k&(1<<i)) x=f[x][i];
	}
	return x;
}
ll query(int x,int y)
{
	memset(px[0][0],0,sizeof(px[0][0]));
	int z=lca(x,y);
	int len=lg[dep[x]-dep[z]];
	hb(0,0,0,0,z,0);
	hb(0,0,0,0,x,len);
	hb(0,0,0,0,kth(x,dep[x]-dep[z]-(1<<len)),len);
	len=lg[dep[y]-dep[z]];
	hb(0,0,0,0,y,len);
	hb(0,0,0,0,kth(y,dep[y]-dep[z]-(1<<len)),len);
	ll ans=0;
	for(int i=60;i>=0;i--)
	if((ans^px[0][0][i])>ans) ans^=px[0][0][i];
	return ans;
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;++i)
	scanf("%lld",&a[i]);
	for(int i=2;i<=n;i++)lg[i]=lg[i/2]+1;
	int x,y;
	for(int i=1;i<n;i++)
	{
		scanf("%d%d",&x,&y);
		add(x,y);add(y,x);
	}
	dfs(1,0);
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&x,&y);
		printf("%lld\n",query(x,y));
	}
}

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号