異或粽子
阿新 • • 發佈:2021-07-29
題面
題解
將題意轉化一下就是,對於一個大小為 \(n\) 的陣列,求出前 \(k\) 大的不重複的區間異或和的和。
我們記錄一下區間字首異或和。那麼一個區間的異或和就可以表示為 \(sum[r]\ xor\ sum[l - 1]\)。
那麼我們要求的就是形如這樣的最大的 \(k\) 對異或值的和。即 \(sum[i]\ xor\ sum[j] (0\leq j < i \leq n)\)。
也就是在一個大小為 \(n + 1\) 的陣列中的 \(\frac{n(n +1)}{2}\) 中組合中選出前 \(k\) 大的。
而這些組合以 \(i, j\) 為長寬的話,對應著一個矩形的情況,而在上述的範圍中是一個不包含對角線的類似於三角形的形狀。
為了使我們尋找更方便,又因為 \(sum[i]\ xor\ sum[j] = sum[j] \ xor \ sum[i]\)
建立一個 \(Trie\) 樹,維護某個數與集合中的某個數排名為 \(rk\) 的異或值。
建立一個大根堆,將 \(0\) ~ \(n\) 的數與集合中的某個數排名為 \(1\) 的異或值,即它的排名插入堆中。
每次取出堆頂,將當前排名的下一個排名插入堆中,取完 \(2k\) 個元素後就得到了前 \(2k\) 大的元素。
因為每次插入堆中的都是每個元素與其它元素的組合中未被選擇的最大異或值,而大根堆維護了這些組合中的最大值,所以取完 \(2k\)
程式碼
#include<cstdio> #include<queue> using namespace std; typedef long long LL; const int N = 5e5 + 5; int n, m; LL a[N]; struct data { int id, rk; LL val; data(int id_ = 0, int rk_ = 0, LL val_ = 0) : id(id_), rk(rk_), val(val_) {} bool operator < (const data &x) const { return val < x.val; } }; priority_queue < data > q; struct Trie { #define M N * 40 int nex[M][2], siz[M], cnt; Trie () { cnt = 0; } void insert(LL x) { int p = 0; for(int i = 31; ~i; i--) { int ch = (x >> i) & 1; siz[p]++; if(!nex[p][ch]) nex[p][ch] = ++cnt; p = nex[p][ch]; } siz[p]++; } LL query(LL x, int rk) { int p = 0; LL ans = 0; for(int i = 31; ~i; i--) { int ch = (x >> i) & 1; if(!nex[p][ch ^ 1]) p = nex[p][ch]; else if(rk <= siz[nex[p][ch ^ 1]]) p = nex[p][ch ^ 1], ans |= 1LL << i; else rk -= siz[nex[p][ch ^ 1]], p = nex[p][ch]; } return ans; } }tr; int main() { scanf("%d%d", &n, &m); for(int i = 1; i <= n; i++) scanf("%lld", &a[i]), a[i] = a[i] ^ a[i - 1]; for(int i = 0; i <= n; i++) tr.insert(a[i]); for(int i = 0; i <= n; i++) q.push(data(i, 1, tr.query(a[i], 1))); m <<= 1; LL res = 0; for(int i = 1; i <= m; i++) { data x = q.top(); res += x.val; q.pop(); if(x.rk < n) q.push(data(x.id, x.rk + 1, tr.query(a[x.id], x.rk + 1))); } printf("%lld\n", res >> 1); return 0; }