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 }
posted @ 2018-03-03 20:49  阿波罗2003  阅读(212)  评论(0编辑  收藏  举报