线性基小计

引入

很早就听说这个高大上的算法了,好像很厉害的样子。
但是 blog 里说的是异或线性基 不是线性代数那个那个看不懂

线性基用来干什么?

  • 查询一个数能不能被一堆数异或出来
  • 查询一堆数异或的最大值
  • 查询异或第 k 大 / 小 (本质不同)

概念

线性基有 3 个性质。

  • 原序列任意数以及任意一堆数的异或可以通过线性基异或出来
  • 线性基任意数异或起来不会产生 \(0\)
  • 满足上述条件时,使得线性基内数最少。

构造

怎么构造一个满足上述条件的线性基呢?
这要扯到线性代数方面,我不好说。
不如直接感性理解。

怎么使得线性基数的个数最小?

我们假设现在已经插入了 \(a_1,a_2,a_3,a_4\) 现在插入 \(a_5\)
怎么办呢?
假设 \(a_5\) 很大,不好插入。
根据异或的性质,实际上,插入 \(a_5\oplus a_1\) 或者 \(a_5\oplus a_2 \oplus a_3\) 是等价 \(a_5\) 的。

明显的,这些数插入都会产生相同的线性基。
那么,我们是不是可以在原有 \(a_1,a_2,a_3,a_4\) 的前提下,构造出一个合法的,最小的 \(a_5\) 插入呢?

线性基的插入

根据上面的思想,我们可以以这样思考:
令最终线性基为 \(d\) ,其中 \(d_i\) 是第 \(i\) 位线性基内存的数,满足最高位是第 \(i\) 位。
明显的,这个线性基的大小不会超过 \(\log v\)

我们思考怎么插入一个数:
不妨令 \(a_1,a_2,a_3,a_4\) 已经构造出一个 \(d\),现在插入 \(a_5\)
从大到小枚举 \(i\) 。如果 \(a_5\)\(i\) 位为 \(1\),分类讨论:

  • \(d_i\) 为空。这时直接插入,退出。
  • 否则令 \(a_5=a_5\oplus d_i\) 继续遍历。

第一个操作好理解,第二个呢?
根据我们刚才的分析,现在的 \(i\) 肯定是 \(a_5\) 的最高位。

这一位无法插入,我们考虑插入一个和 \(a_5\) 等价的,也许是 \(a_5\oplus a_3\) ,然后把这一位删除,考虑下一位。

不妨考虑这样有什么后果。

  • 找到下一位为空的 \(d_i\)
  • 找不到为空的 \(d_i\)

第一种情况直接就结束了,我们考虑情况 \(2\)
找不到为空的 \(d_i\) ,可以证明,现在 \(a_5=0\) 因为我们已经从小到大把 \(a_5\) 所有位数都给删掉了。

这说明,我们可以在 \(a_1,a_2,a_3,a_4\) 中构造出一种异或,使得它们的异或和为 \(a_5\)
这时候,\(a_5\) 不需要插入。

这样,我们构造出了满足 \(1,2\) 的线性基。

因为线性基内的数总是小于原数组的数,所以 \(3\) 也满足。
因此,构造完毕。

异或最值

Max

很简单,因为每一位线性基最高位是确定的,从后往前贪心即可。

Min

根据每一位是确定的,从前往后找到第一个有数的基即可。

Kth

不会bx

前缀线性基

link
考虑两种写法。
无脑分制很简单,这不讲了。

我们考虑前缀线性基。
明显,我们可以把所有时候,定义状态 \(f_i\) 表示 \([1,i]\) 的线性基,并且满足每一位最大。
这怎么搞?
一种显然的想法,从第 \(i\) 为开始往后插入。
但是这不好搞,时间不允许。
假设我们已经求出 \([1,i-1]\) 满足每一位最大。
现在我们插入 \(i\)
考虑第 \(i\) 位肯定是要插入的。我们要把哪一位取出来。
根据贪心,后面的取出来可以从前面插入。
如果前面插入不了了,那这一位肯定要插入进去,如果大可以在后面考虑。可以发现此时插入一定不劣。

我们可以理解为把这一位基拿出来,然后塞进去,得到一个同等的子问题,但是 pos 变小。后面 pos 贪心搞即可。

  • 例题
    P3292
    你不会写没关系,淀粉质直接薄纱 dijah 前缀线性基做法,知不知道 \(O(n\log^2 n)\) 快过理论 \(O(n\log n)\) 轰啊
点击查看代码
#include<bits/stdc++.h>
#define N 20005
#define ll long long
using namespace std;
int n,m;
int head[N],tot=1;
struct edge{
	int to,next;
}e[N*2];
void add(int u,int v)
{
	e[tot]=(edge){v,head[u]};
	head[u]=tot++;
}
ll w[N],ans[N*10];
struct node{
	int x,id;	
};
vector<node> q[N];
int root,minn;
int siz[N],vis[N];
struct Xor{
	ll d[62];
	void insert(ll x)
	{
		for(int i=60;i>=0;i--)
			if(x&(1ll<<i)) 
				if(d[i]) x^=d[i];
				else {d[i]=x;return ;}
	}
	ll ask()
	{
		ll sum=0;
		for(int i=60;i>=0;i--)
			sum=max(sum,sum^d[i]);
		return sum;
	}
}f[N];
Xor merges(Xor a,Xor b)
{
	for(int i=0;i<=60;i++)
		if(a.d[i]) b.insert(a.d[i]);
	return b; 
}
void groot(int now,int fa,int n)
{
	int maxx=0;
	siz[now]=1;
	for(int i=head[now];i;i=e[i].next)
	{
		int son=e[i].to;
		if(son==fa||vis[son]) continue;
		groot(son,now,n);
		siz[now]+=siz[son];
		maxx=max(maxx,siz[son]);
	}
	maxx=max(maxx,n-siz[now]);
	if(maxx<minn) minn=maxx,root=now;
}
int col[N],ids;
void getdis(int now,int fa)
{
	col[now]=ids;
	f[now]=f[fa],f[now].insert(w[now]); 
	siz[now]=1;
	for(int i=head[now];i;i=e[i].next)
	{
		int son=e[i].to;
		if(son==fa||vis[son]) continue;
		getdis(son,now);
		siz[now]+=siz[son];
	}
}
void getans(int now,int fa)
{
	for(int i=0;i<q[now].size();i++)
		if(col[q[now][i].x]^col[now])
		{
			ans[q[now][i].id]=merges(f[now],f[q[now][i].x]).ask();
			swap(q[now][i],q[now][q[now].size()-1]);
			q[now].pop_back();
			i--;
		}
	for(int i=head[now];i;i=e[i].next)
	{
		int son=e[i].to;
		if(son==fa||vis[son]) continue;
		getans(son,now);
	}
}
void solve(int now,int n)
{
	minn=n+2;
	groot(now,0,n);
	vis[root]=1;
	memset(f[root].d,0,sizeof f[root].d);
	f[root].insert(w[root]);
	col[root]=ids=0;
	for(int i=head[root];i;i=e[i].next)
	{
		int son=e[i].to;
		if(vis[son]) continue;
		++ids;
		getdis(son,root);
	}
	getans(root,0);
	for(int i=head[root];i;i=e[i].next)
	{
		int son=e[i].to;
		if(vis[son]) continue;
		solve(son,siz[son]);
	}
}
int main()
{
//	freopen("c.in","r",stdin);
//	freopen("m.txt","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%lld",&w[i]);
	for(int i=1;i<n;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		add(u,v);
		add(v,u);
	}
	for(int i=1;i<=m;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		if(u^v) q[u].push_back((node){v,i});
		else ans[i]=w[u];
	}
	solve(1,n);
	for(int i=1;i<=m;i++) printf("%lld\n",ans[i]);
	return 0;
}

求有多少个数比 \(x\)

使用贪心。
把每一位线性基里面的数拿出来。
如果 \(x\) 这一位是 \(1\) 且 存在 \(d\) 这一位也是 \(1\)

那么我们把 前面有的位数全取了,后面随便取即可。
否则退出。

posted @ 2024-03-20 09:44  g1ove  阅读(10)  评论(0编辑  收藏  举报