【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;
}
posted @ 2021-01-30 18:32  Jayun  阅读(52)  评论(0编辑  收藏  举报