线性基学习笔记

\(#defing ll long long\)
线性基用处:
 快速查询一个数是否可以被一堆数异或出来
 快速查询一堆数可以异或出来的最大 \(/\) 最小值
 快速查询一堆数可以异或出来的第 \(k\) 大值

线性基空间复杂度:
 设有一个序列,其值域为 \([1,N]\),我们可以构造一个长度为 \(⌈\log_2 N⌉\) 的线性基。
 其中,满足性质:序列能异或出来的数,线性基都能异或出来,反之,线性基能异或出来的数,序列都能异或出来。
 证明一定存在一种构造的线性基能满足这个性质。
 不妨设整个序列的最大值为 \(N\),则一定存在 \(i=⌈\log_2 N⌉\) 满足 \(2^{i-1}< N\le 2^i\),则这个序列最多最多,只能异或出 \(2^i\) 个数,而我们线性基的所有组合也是 \(2^i\),只要构造出的线性基满足每种组合不一样即可(感性理解,感性裂解)

贪心构造线性基:
 令线性基为数组 \(a\)\(a_i\) 最高位表示 \(2^i\)
 考虑对于一个数 \(x\),从高到低每一位枚举,如果 \(x\) 在第 \(j\) 位为 \(1\)
 若 \(a_j\) 没有记录,则令 \(a_j=x\)
 否则的话,令 \(x=x \oplus a_j\)
 最后在特判一下,看一下 \(x\) 是否为 \(0\)
 若是,则记录一下,表示这些数可以异或出来 \(0\)

void ins(ll x)
{
  for(ll i=N;i>=0;i--)
  	if(x&(1ll<<i))
  		if(!a[i]){a[i]=x;return ;}
  		else x^=a[i];
  flag=true;
}  

判断一个数能否被序列异或出来:
 跟构造差不多,只不过变成了直接\(return\) \(true/false;\)罢了

bool check(ll x)
{
    for(ll i=N;i>=0;i--)
        if(x&(1ll<<i))
            if(!a[i])return false;
            else x^=a[i];
    return true;
}

查询异或最小值:
 首先看一下能否表示 \(0\)
 若不能,因为线性基的位数都是单调递减的,任意两个数异或出的数必然会大于小一点的那个数,所以直接输出线性基的最小值即可

ll qmin()
{
    if(flag)return 0;
    for(ll i=0;i<=N;i++)
        if(a[i])return a[i];
}

查询异或最大值:
 从高到低枚举线性基 \(a_i\),在异或和不异或之间取 \(max\) 即可。

ll qmax()
{
	ll re=0;
	for(ll i=N;i>=0;i--)
		re=max(re,re^a[i]);
	return re;
}

查询异或\(k\)小值:
 个人认为这个比较难懂,
 首先,先特判\(0\)
 然后要构造出一个 \(b\) 数组,\(b_i\) 表示最高位为 \(2^i\) 且该位为 \(1\) 时能表示出的最小异或值
 很显然,有些 \(b_i\) 是不存在,所以我们可以构造一个 \(tmp\) 数组,\(tmp_j\) 表示从小到大第 \(j\) 个可以表示的 \(b_i\)。(感性理解)
 设 \(tmp\) 长度为 \(cnt\),可以发现我们最多构造出 \(2^{cnt}-1\)个数,所以如果有解必然是 \(2^{cnt} > k\)
 不难发现,第 \(2^i\) 小的数就是 \(tmp_i\),第 \((2^i+2^j)\) 小的数就是 \(tmp_i \oplus tmp_j\)
 那么就有:

ll query(ll k)
{
    ll re=0;
	ll cnt=0;
    k-=flag;
	if(!k)return 0;
    for(ll i=0;i<=N;i++)
	{
        for(int j=i-1;j>=0;j--)
            if(a[i]&(1ll<<j)) a[i]^=a[j];
        if(a[i]) tmp[cnt++]=a[i];
    }
    if(k>=(1ll<<cnt)) return -1;
    for(ll i=0;i<cnt;i++)
        if(k&(1ll<<i)) re^=tmp[i];
    return re;
}
posted @ 2023-08-23 16:43  傻阙的缺  阅读(25)  评论(0)    收藏  举报