线性基
线性基的定义
线性基说白就是就一个集合,满足对于一个集合而言,我这个集合能异或得到的数,我线性基也能凑出来。当然,广义上的线性基也可以不是线性基,那就是向量线性基的事了。
线性基的构造
考虑到我们要让线性基集合大小尽可能小,不难想到我们可以对于每一位考虑。
我们假设 (d_i) 表示第 (i) 位大小为 (1) 的一个可以异或出来的数。对于要加进去的数,如果当前第 (i) 位为 (1),那么我们如果以前得到过 (d_i) ,我们显然可以用过异或上 (d_i) 来消掉当前位。否则,则说明我们这一位无论怎么异或都无法变为 (0),则我们就找到了一个与当前线性基线性无关的数,直接把 (d_i) 赋为当前值即可。
不难看出,这样我们原集合能够表示的数,我们线性基一定可以表示。同时,线性基的大小也控制在了 (log w),其中 (w) 是值域。
线性基的一些性质
- 若线性基大小为 (s),那么能够表示的不同的数有 (2^s) 个。
不难看出,对于每一位,我们有选或者不选的情况。
- 对于一个集合,它能表示的数出现次数一定相同。
似乎并不是知道怎么正经地证?不过似乎可以直接组合数来证明。
- 对于某一位,能表示的数(不重复)中有 (2^{s-1}) 个出现过。
证明由上一个性质可以得到。
- 线性基每一个 (d_i) 比它高的位都为 (0)。
证明显然。
P.S.
话说正经的线性基还需要满足一个条件就是说线性基里的一位并不会被其它的数包含,不过有些时候并不使用这个性质,可以直接乱插。
线性基的一些应用
- 查找表示的数中最大值/最小值
不难看出,直接从高到低贪心即可,因为当前变 (1) 比后面都变 (1) 显然更优。
- 查找能表示的数的第 (k) 大
这个时候就需要用 P.S. 里面那个性质了,不难看出这个时候,我当前选更高位一定比选后面的位答案更大,选了更高位后面更低位一定只会让答案增加。考虑 (k) 的二进制拆分,对于更高位,一定会被后面较低位更高,选了当前位再选较低位一定会增加。可以看出两个性质相同,然后直接来就好了
解法就比较显然了 —— 枚举 (k) 所有为 (1) 的二进制位,如果第 (i) 位为 (1),则将线性基中控制的第 (i) 小的二进制位的元素异或到答案中。
当然了,还有另外一种理解方法,我们可以把线性基看成一个二叉树,不选更高位一定比选了更高位更劣(因为这个 P.S. 的性质保证了一定不会让当前答案的当前考虑位为 (1))。然后就完了。
- 线性基的合并
直接暴力一个线性基插到另一个线性基里面就好了,反正本质上都是集合。
- 线性基的删除
直接线段树分治即可。时间复杂度不考虑其他操作是 (nlog^2 n) 的。
线性基的一些题目
[WC2011]最大XOR和路径
思路
不难看出我们可以把走的到的环加进路径而不影响其他权值,所以直接把环的权值加进线性基里面然后查询就好了。
( exttt{Code})
CF724G Xor-matic Number of the Graph
思路
跟上面一个题其实差不多,可以对于每一位进行考虑。(s) 是线性基的大小。
- 线性基里面有数包含这一位
那么,因为我们无论怎么搞都可以让这一位为 (1),所以方案就是 (inom{n}{2} imes 2^{s-1}),这个可以由性质3得到。
- 线性基里面没有数包含这一位
那么能够表示的数肯定也不包含这一位,于是方案就是 (t imes (n-t) imes 2^s),其中 (t) 是这一位权值为 (1) 的点数。
然后就完了。
( exttt{Code})
#include <bits/stdc++.h>
using namespace std;
#define Int register int
#define int long long
#define MAXN 100005
#define MAXM 400005
template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;}
template <typename T,typename ... Args> inline void read (T &t,Args&... args){read (t);read (args...);}
template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}
int n,m;
struct edge{
int v,w,nxt;
}e[MAXM];
int toop = 1,head[MAXN];
void Add_Edge (int u,int v,int w){
e[++ toop] = edge {v,w,head[u]},head[u] = toop;
e[++ toop] = edge {u,w,head[v]},head[v] = toop;
}
bool vis[MAXN];
int t,siz,Su[MAXN],val[MAXN];
int a[61];
void ins (int x){
for (Int i = 60;~i;-- i) if (x >> i & 1){
if (a[i]) x ^= a[i];
else{
++ siz,a[i] = x;
break;
}
}
}
void dfs (int u,int dis){
vis[u] = 1,Su[++ t] = u,val[u] = dis;
for (Int i = head[u];i;i = e[i].nxt){
int v = e[i].v,w = e[i].w;
if (!vis[v]) dfs (v,dis ^ w);
else ins (dis ^ w ^ val[v]);
}
}
#define mod 1000000007
int ans,pw[MAXN];
int mul (int a,int b){return 1ll * a * b % mod;}
int dec (int a,int b){return a >= b ? a - b : a + mod - b;}
int add (int a,int b){return a + b >= mod ? a + b - mod : a + b;}
signed main(){
read (n,m);pw[0] = 1;for (Int i = 1;i <= n;++ i) pw[i] = pw[i - 1] * 2 % mod;
for (Int i = 1,u,v,w;i <= m;++ i) read (u,v,w),Add_Edge (u,v,w);
for (Int u = 1;u <= n;++ u) if (!vis[u]){
memset (a,0,sizeof (a)),t = siz = 0,dfs (u,0);
for (Int i = 0;i <= 60;++ i){
bool flg = 0;
for (Int j = 0;j <= 60;++ j) if (a[j] >> i & 1){flg = 1;break;}
if (flg) ans = add (ans,t * (t - 1) / 2 % mod * pw[siz - 1] % mod * pw[i] % mod);
else{
int x = 0;
for (Int j = 1;j <= t;++ j) x += (val[Su[j]] >> i & 1);
ans = add (ans,x * (t - x) % mod * pw[siz] % mod * pw[i] % mod);
}
}
}
write (ans),putchar ('
');
return 0;
}