bzoj 2844 albus就是要第一个出场 - 线性基
题目传送门
这是个通往vjudge的虫洞
这是个通往bzoj的虫洞
题目大意
给定集合$S$,现在将任意$A\subseteq S$中的元素求异或和,然后存入一个数组中(下标从1开始),然后从小到大排一个序。问$q$第一次出现在$A$中的下标。
我们可以通过线性基得到值域上有多少个异或和比$q$小,现在问题来了,怎么求$q$的下标。
通过打表找规律,以及手动枚举可以发现一个结论。
定理1 设线性基为$B$,那么在$S$的子集的异或和中,出现的异或和的出现的次数是$2^{\left | S \right |- \left | \mathfrak{B} \right |} $。
证明 假如要考虑异或出一个数$x$,基外选出的数的异或和为$s$,那么还需要$x$ ^ $s$,对于它,基内的线性表示的方法是唯一的。
所以定理得证。
于是再做一次快速幂,这道题就做完了。
PS:这道题数据有错,它没有保证$q$一定能被异或出来
Code
1 #include <bits/stdc++.h> 2 using namespace std; 3 typedef bool boolean; 4 5 const int MAX_BASE = 31, M = 10086; 6 7 int qpow(int a, int pos) { 8 int pa = a, rt = 1; 9 for ( ; pos; pos >>= 1, pa = pa * pa % M) 10 if (pos & 1) 11 rt = rt * pa % M; 12 return rt; 13 } 14 15 int n, q; 16 int *ar; 17 int b[MAX_BASE]; 18 int s[MAX_BASE]; 19 20 inline void init() { 21 scanf("%d", &n); 22 ar = new int[(n + 1)]; 23 for (int i = 1; i <= n; i++) 24 scanf("%d", ar + i); 25 scanf("%d", &q); 26 } 27 28 int cnt = 0, rk = 0; 29 inline void solve() { 30 for (int i = 1; i <= n; i++) { 31 for (int j = MAX_BASE - 1; ~j; j--) { 32 if (ar[i] & (1 << j)) ar[i] ^= b[j]; 33 if (ar[i] & (1 << j)) { 34 b[j] = ar[i]; 35 cnt++; 36 break; 37 } 38 } 39 } 40 for (int i = 0; i < MAX_BASE; i++) 41 if (b[i]) 42 s[i] = 1; 43 for (int i = 0; i < MAX_BASE; i++) 44 s[i] += s[i - 1]; 45 for (int i = MAX_BASE - 1; ~i; i--) 46 if ((q & (1 << i)) && b[i]) 47 rk |= (1 << (s[i] - 1)); 48 printf("%d\n", ((rk % M) * qpow(2, n - cnt) + 1) % M); 49 } 50 51 int main() { 52 init(); 53 solve(); 54 return 0; 55 }