线性基
别问我为什么现在才开始学线性基。。。
看到我们机房巨佬去年10月份就已经切了线性基,简直无地自容。
好了现在进入正题。
线性基
性质:①线性基能相互异或得到原集合的所有相互异或得到的值。
②线性基是满足性质1的最小的集合。
③线性基没有异或和为0的子集。
应用
我们知道,运用线性基中的一些元素进行异或,可以得到原序列中的任意元素(当然针对性质3,序列中的0需要单独讨论),利用这样的性质,我们就可以解决很多OI中的关于元素异或的问题了!(*^▽^*)
-
线性基的构造
我们用a数组来存储原序列中的元素,用b数组来存储线性基中的元素。我们可以尝试将a中的元素插入线性基。
1 for(int i=1;i<=n;i++) 2 { 3 ll x=a[i]; 4 for(int j=50;j>=0;j--) 5 if(x&(1ll<<j)) //注意当j超过31时要用1ll,不然会爆掉(QAQ我就在这调了好久) 6 { 7 if(!b[j]) 8 { 9 b[j]=x; 10 break;//插入成功后记得退出哦! 11 } 12 else x^=b[j]; 13 } 14 }
对于x而言,每次能进行插入的实质上是它的最高位(二进制下),如果插入成功就可以直接退出,否则与b数组该位的元素进行异或(通过手玩推理可以发现这样插入可以保证能异或出序列中的原元素)。
-
查询最大值
对于查询序列中能异或出的最大值,我们可以采用贪心的思想,把b数组从大往小枚举,如果能够使当前答案变大,就异或上该b数组中的元素。
1 ll qmax() 2 { 3 ll ans=0; 4 for(int i=50;i>=0;i--) if((ans^b[i])>ans) ans^=b[i]; 5 return ans; 6 }
证明:对于每一个非0的bi,其二进制上的i+1位都为1,如果当前答案异或上改元素大于原答案,证明原答案第i+1位上为0,因为是从大往小枚举,因此异或只会对答案i+1位以后产生影响,且就算后i位全从1变成0也比第i+1位从0变成1的贡献小,故该贪心是正确的。
-
查询最小值
考虑b数组各元素最高位不同,故每次由小的b异或大的b都会使小的b变大,且b的值随i值的增大而增大(非零状况下),所以可以分两类考虑:
①当原序列能异或出0时,直接输出0;
②如果原序列不能异或出0,那么将b数组由i值从小到大枚举,第一个非0的b值即为答案。
1 ll qmin() 2 { 3 if(fl) return 0;//fl==1指能异或出0 4 for(int i=0;i<=50;i++) if(b[i]) return b[i]; 5 }
-
查询第k小
首先,我们要简化一下我们的b数组。
1 void pre() 2 { 3 for(int i=1;i<=50;i++) 4 { 5 for(int j=i-1;j>=0;j--) 6 if(b[i]&(1<<j)) b[i]^=b[j]; 7 if(b[i]) c[cnt++]=b[i]; 8 } 9 cnt--; 10 }
大家可以发现,简化完了后,对于二进制上的每一位,b数组中该位上为1的元素个数<=1。而现在的这个b数组与原数组实质上是相同的,都可以通过异或得到原序列的每一个元素。
然后,我们用一个c数组记录b数组中对我们的答案有贡献的元素(即不为0的元素)。
我们对k进行转化,转化为二进制数。对于k的每一个为1的二进制位(假设为i),我们用c[i]异或上我们当前的答案。
1 ll qk_th(int k) 2 { 3 if(fl) k--; 4 if(!k) return 0; 5 if(k>=(1<<cnt)) return -1; 6 ll ans=0;int t=0; 7 while(k) 8 { 9 if(k%2) ans^=c[t]; 10 k/=2; 11 t++; 12 } 13 return ans; 14 }
这样做是正确的。因为c数组是递增的,而且每一个数1的分布不同,可以证明,形如11…比形如10…(假设位数相同)的结果大,位数大的比位数小的大,因此异或出的结果一定是第k大的(当然要对0的情况特判)。注意有贡献的状态有cnt个,最多能异或出的非0的状态为(1<<cnt)-1个,若k大于总状态数,则无解。
(未完待续...)

浙公网安备 33010602011771号