【Luogu P5283】[十二省联考2019]异或粽子
链接:
题目大意:
求一段数列前 \(k\) 大区间异或和的和。
正文:
本题和 【Luogu P2048】[NOI2010] 超级钢琴 思路极其相似。对于前 \(k\) 大,我们可以用堆来维护。
不同于超级钢琴的是,本题是异或和,不能直接根据前缀和大小排序,那怎么办呢?考虑用 0/1Trie 解决。从高位到低位插入数字,顺便记录子树大小,至于查询,毕竟是第 \(k\) 大,我们可以像平衡树一样通过子树大小确定当前一位。
代码:
const int N = 2e7 + 10;
int n, k;
ll sum[N];
struct Trie
{
int ch[N][2];
ll siz[N], tot;
void ins(ll val)
{
int u = 0;
for (int i = 31; ~i; --i)
{
bool k = (val >> i) & 1; siz[u]++;
if (!ch[u][k]) ch[u][k] = ++tot;
u = ch[u][k];
}
siz[u] ++;
return;
}
ll query(ll val, int n)
{
int u = 0;ll ans = 0;
for (int i = 31; ~i; --i)
{
bool k = (val >> i) & 1;
if(!ch[u][k ^ 1]) u = ch[u][k];
else if (n <= siz[ch[u][k ^ 1]]) u = ch[u][k ^ 1], ans |= 1ll << i;
else n -= siz[ch[u][k ^ 1]], u = ch[u][k];
}
return ans;
}
}t;
struct node
{
ll x, rk, val;
inline bool operator < (const node& a) const
{
return val < a.val;
}
};
priority_queue<node> q;
ll ans = 1;
int main()
{
// freopen(".in", "r", stdin);
// freopen(".out", "w", stdout);
scanf ("%d%d", &n, &k);
t.ins(0);
for (int i = 1; i <= n; i++)
scanf ("%lld", &sum[i]), sum[i] ^= sum[i - 1], t.ins(sum[i]);
for (int i = 0; i <= n; i++)
q.push((node){i, 1, t.query(sum[i], 1)});
for (int i = 1; i <= 2 * k; i++)
{
node x = q.top(); ans += x.val; q.pop();
if (x.rk < n) q.push((node){x.x, x.rk + 1, t.query(sum[x.x], x.rk + 1)});
}
printf ("%lld\n", ans / 2);
return 0;
}