====== 线性基 ====== ===== 定义:===== ==== 张成: ==== * $T \subseteq S$,所有这样是子集$T$的异或和组成的集合称为$S$的**张成**记作$span(S)$。 ==== 线性相关: ==== * 存在一个元素$S_j$可以用其它若干元素异或起来得到 * 或:存在一个子集的异或和为0 ==== 线性基:==== === 称$B$是$S$的线性基当且仅当: === - $S \subseteq span(B)$,即$S$是$B$的张成的子集。 - $B$是线性无关的。 === 线性基的长度: === * 集合$B$中元素的个数 === 线性基的基本性质: === - $B$是极小的满足线性基性质的集合,它的任何真子集都不可能是线性基; - $S$中的任意元素都可以唯一表示为$B$中若干个元素异或起来的结果。 ===== 构造:===== 线性基是动态构造的,我们只需要从空的$a$开始,每次考虑在一个已存在的线性基中插入一个数$t$即可。 ==== 构造流程: ==== 对于每个数$x$,从高位到低位扫描,扫到第$i$位为1时,若$a_i$上有数字$x=x \oplus a_i$,并向下继续扫,若没有数字$a_i=x$结束扫描。 inline void insert(ll x){ for(int i=50;i>=0;i--)if(x&(1ll< ==== 大致证明: ==== === 未成功插入线性基: === 考虑时候不能插入进去呢,显然就是它在尝试插入时异或若干个数之后变成了0 $\Rightarrow$ 新插入的数$x$可由当前的线性基异或表示 $\Rightarrow$ 所有数都能由当前的线性基异或表示。 === 成功插入线性基: === 假设$x$成功插入到了$a_i$,显然,它在插入前可能异或若干个数,那么就有:\\ $x \oplus a_j \oplus a_k \oplus ... = a_i$\\ 即:$a_i \oplus a_j \oplus a_k \oplus ... = x$\\ $\Rightarrow$ 新插入的数x可由当前的线性基异或表示 $\Rightarrow$ 所有数都能由当前的线性基异或表示。 ==== 性质: ==== 构造出来的线性基满足: - 若$a_i=0$: * **只有**满足$j>i$的$a_j$的第i位才**可能**为1 - 若$a_i\ne0$: * 整个$a$数组只有$a_i$的第$i$位二进制位1 * $a_i$的更高位($>i$的位数)**一定**为0 * $a_i$的更低位($ inline bool check(ll x){ for(int i=50;i>=0;i--) if(x&(1ll< ==== 求数集能异或出的最大值: ==== 使用贪心的思想: inline ll getMax(){ ll ans=0; for(int i=50;i>=0;i--) ans=max(ans,ans^a[i]); return ans; } **证明:**\\ $a_i$的第$i$位一定是1,而后面扫描到的$a_{i-1},a_{i-2},...,a_0$第$i$位一定不是1,而如果高位不是1低位再多1也弥补不了,所以当前只需要考虑第$i$位的情况,若$ans$的第$i$位为0异或后为1会变大就进行异或,若为1异或后为0会变小就不进行异或。 ==== 求数集能异或出的最小值: ==== 显然是最小的一个$a_i$,最小的%a_i%异或其它数字都会变大。 ==== 求能异或出的第$k$小值: ==== 从一个序列中取任意个元素进行异或,求能异或出的所有数字中第$k$小的那个。\\ 首先对线性基进行改造,保证只有$a_i$的第$i$位是1,其它的第$i$位都为0。\\ 改造方法是若$a_i$的第$j$位为1$(j void init(){//处理线性基 for(int i=1;i<=50;i++) for(int j=0;j>=1; } return ans; } **证明(或者说是理解):**其实处理完之后的线性基其实也还是原序列的一个线性基,回想上面的线性基处理过程,可以发现,处理完之后,线性基中的元素,作用其实都是提供自己最高位上的 1,那么只要使提供出来的 1 可以和 $k$的二进制的每一位上的 1对应,那么求出的$ans$一定就是第$k$小的。 ===== 题目:===== ==== 洛谷4570 [BJWC2011]元素 ==== **题意:**给定一些矿石编号和矿石价值,问在保证矿石编号的任意子集的异或和不为0的情况下,最大价值是多少。\\ **题解:**很显然的贪心,按价值从大到小排序维护线性基。\\ #include #include #include #include #include #include #include #include #include #include #include using namespace std; typedef long long ll; typedef pair pi; const int N=1005; ll getint(){ char c;bool flag=0;ll num=0ll; while((c=getchar())<'0'||c>'9')if(c=='-')flag=1; while(c>='0'&&c<='9'){num=num*10+c-48;c=getchar();} if(flag) num=-num; return num; } int n,ans;pi x[N];ll a[65]; inline bool insert(ll x){ for(int i=60;i>=0;i--)if(x&(1ll< \\ ==== 洛谷3292 [SCOI2016]幸运数字 ==== **题意**:一棵树每个点上有权值,多次查询不同路径x->y的最大幸运值。幸运值:从x->y路径上的点选任意子集的异或和。\\ **题解**:倍增lca+线性基\\ #include #include #include #include #include #include #include #include #include #include #include using namespace std; typedef long long ll; typedef pair pi; const int N=20005; ll getint(){ char c;bool flag=0;ll num=0ll; while((c=getchar())<'0'||c>'9')if(c=='-')flag=1; while(c>='0'&&c<='9'){num=num*10+c-48;c=getchar();} if(flag) num=-num; return num; } int n,q,fa[N],dep[N]; ll luck[N],A[N][17][63],f[N][17],a[63]; int cnt,fir[N],tar[N<<1],nxt[N<<1]; inline void link(int a,int b){ tar[++cnt]=b,nxt[cnt]=fir[a],fir[a]=cnt; } void insert(ll*a,ll x){ for(int i=60;i>=0;i--)if(x&(1ll<=0;i--) ans=max(ans,ans^a[i]); return ans; } int getk(int x,int k){ if(!k) return x; for(int i=15;i>=0;i--)if(k&(1<=0;i--) if(f[x][i]!=f[y][i]){ merge(a,A[x][i]),x=f[x][i]; merge(a,A[y][i]),y=f[y][i]; } insert(a,luck[f[x][0]]); return getMax(); } int main(){ n=getint(),q=getint(); for(int i=1;i<=n;i++) luck[i]=getint(); for(int i=1;i \\ ==== 洛谷3857 [TJOI2008]彩灯 ==== **题意:**每个开关可以控制一部分的灯,改变它们的开关状态,计算灯的状态有多少种可能。(对2008取模)灯的数量和开关的数量都小于50\\ **题解:**设线性基的长度为$x$,答案为$2^x$ #include #include #include #include #include #include #include #include #include #include #include using namespace std; typedef long long ll; const int N=20005; int n,m;ll a[55];char str[55]; inline void insert(ll x){ for(int i=50;i>=0;i--)if(x&(1ll<>=1; } return ans; } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=m;i++){ scanf("%s",str+1); ll x=0ll; for(int j=1;j<=n;j++){ x<<=1; if(str[j]=='O') x|=1; } insert(x); } int cnt=0; for(int i=0;i<=50;i++) if(a[i]) cnt++; printf("%d\n",qpow(2,cnt)); } \\ ==== 洛谷4151 [WC2011]最大XOR和路径 ==== **题意:**给一个无向连通图,求一条从$1$到$n$的路径(可以不是简单路径),使经过的边权的异或和最大。\\ **题解:**首先注意到一个结论:对于所有的简单环,环上边权的异或和都可以无代价的获取。原因是可以从一号点出发进入该环绕一圈后原路返回。由于一条路径绕两边对答案的贡献为 $0$,所以这些简单环的异或和都可以无代价取得。那么现在问题就转化成了寻找一条 $1$到 $n$的路径,再异或上一些简单环的异或和,最大化答案。\\ 我们考虑应该寻找哪一条路径:事实上任选一条路径即可。原因是选择的路径和答案路径一定可以构成一个环,所以异或上该环的权值就可以得到最优解。 #include #include #include #include #include #include #include #include #include #include #include using namespace std; typedef long long ll; const int N=50005; const int M=200005; ll getin(){ char c;bool flag=0;ll num=0ll; while((c=getchar())<'0'||c>'9')if(c=='-')flag=1; while(c>='0'&&c<='9'){num=num*10+c-48;c=getchar();} if(flag) num=-num; return num; } int n,m,cnt=1,fir[N],tar[M],nxt[M]; ll w[M],f[N],a[65];bool dfn[N]; inline void link(int a,int b,ll c){ tar[++cnt]=b,w[cnt]=c; nxt[cnt]=fir[a],fir[a]=cnt; } inline void insert(ll x){ for(int i=61;i>=0;i--)if(x&(1ll<=0;i--) ans=max(ans,ans^a[i]); cout< \\