其他分享
首页 > 其他分享> > 【题解】P5283 [十二省联考 2019] 异或粽子(字典树 Trie,优先队列)

【题解】P5283 [十二省联考 2019] 异或粽子(字典树 Trie,优先队列)

作者:互联网

【题解】P5283 [十二省联考 2019] 异或粽子

很好的优先队列+可持久化字典树练手题!

题目链接

P5283 [十二省联考 2019] 异或粽子 - 洛谷

题意概述

给定长度为 \(n\) 的序列 \(a_i\)。一个区间 \([l,r](1 \le l \le r \le n)\) 的价值为从 \(a_l\) 到 \(a_r\) 之间的每个数字进行的异或值。求这个序列中价值最大的 \(k\) 个区间的价值和。

思路分析

从问题入手。

首先求价值最大的 \(k\) 个区间的价值和,这一看就可以用优先队列(大根堆)来维护。

然后考虑如何快速的求出一个区间 \([l,r]\) 的价值。

由于异或也满足前缀和的性质,所以设 \(S_i\) 表示从 1 到 \(i\) 的异或和,则 \([l,r]\) 的价值就是 \(S_r \oplus S_{l-1}\)。

那么如何找到价值最大的 \(k\) 个区间呢?

首先我们可以预处理出来每一个 \(S_i\)(就像预处理前缀和那样),然后对于每一个 \(S_i\) 找到对应的 \(S_j\) 将其价值放入优先队列中。每次从优先队列中取出堆顶,累加进答案。

若当前取出的是 \(S_i\) 和对应的第 \(t\) 大值,则将和 \(S_i\) 对应的第 \(t+1\) 大值放入优先队列中。直到优先队列被取出堆顶 \(k\) 次。

但是有一个问题:我们在查找对于 \(S_i\) 第 \(t\) 大值时,是在整个所有区间的价值中查找的。假如在查找与 \(S_i\) 对应的值时查找到了 \(S_j\),那么在查找 \(S_j\) 的时候又会查一遍 \(S_i\)。也就是说,同一个区间的价值 \(S_i \oplus S_j\) 可能会入队两次,这样就会导致答案算重。

那么怎么解决呢?

或许我们会说,对于一个 \(i\),在查找的时候只查找 \(j>i\) 的 \(S_j\),但是这样显然并不好维护。

有一个方法就是,由于 \(S_i\) 和 \(S_j\) 互为对偶,所以我们可以继续这样查找,但是这次变成了让优先队列被取出栈顶 \(2k\) 次,那么答案就是总取出栈顶 \(2k\) 次的和除以 2。

现在问题就转化为:

已知一个序列 \(S_i\),求对于每一个 \(S_i\),序列中与其异或值第 \(t\) 大的值是多少?

先不考虑第 \(t\) 大,如果求的是最大。那么我们可以用类似于最长异或路径 - 洛谷中的额方法来维护,即:

建立一棵 01Trie,对于每一个 \(S_i\),将其插入 01Trie,对于其二进制下的每一位在 01Trie 中尽量寻找和这一位不相同的,若找不到不相同的,则找相同的,一路走到叶子节点,找到的就是字典树上与其异或路径最大的点。

现在考虑第 \(t\) 大。

可以模仿在学习可持久化线段树(主席树)时我们查找树上第 \(k\) 大的值时的做法:

在字典树插入过程中,记录每个节点的数字数量。

在查询的时候,根据优先往 0 走还是往 1 走,以及左右子节点数量和 \(t\) 来决定下一步的方向。

代码实现如下:

int find(int x,int num)
{
    int now=0,ret=0;
    for(int i=31;i>=0;i--)
    {
        int k=(x>>i)&1;
        if(vis[son[now][k^1]]>=num)//如果 k^1 中的值够用。
        {
            now=son[now][k^1];
            //ret=(ret<<1)+(k^1);
            ret|=(1ll<<i); 
        }
        else
        {
            num-=vis[son[now][k^1]];//需要减去 k^1 的大小。
            now=son[now][k];
            //ret=(ret<<1)+k;
        }
    }
    return ret;
}

求解步骤

易错点

经验

代码实现

//luoguP5283
#include<iostream>
#include<cstdio>
#include<queue>
#define int long long
using namespace std;
const int maxn=5e5+100;
int sum[maxn],son[maxn*32][2],vis[maxn*32],tot;
int ans;

struct Node
{
	int w,id,num;
	bool operator<(const Node &t)const
	{
		return w<t.w;
	}
}d[maxn];

priority_queue<Node>q;

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
 } 
 
void ins(int x)
{
	int now=0;
	for(int i=31;i>=0;i--)
	{
		int k=(x>>i)&1;
		if(!son[now][k])son[now][k]=++tot;
		now=son[now][k];vis[now]++;
	}
	//vis[now]++;
}

int find(int x,int num)
{
	int now=0,ret=0;
	for(int i=31;i>=0;i--)
	{
		int k=(x>>i)&1;
	    if(vis[son[now][k^1]]>=num)
		{
			now=son[now][k^1];
			//ret=(ret<<1)+(k^1);
			ret|=(1ll<<i); 
		}
		else
		{
			num-=vis[son[now][k^1]];
			now=son[now][k];
			//ret=(ret<<1)+k;
		}
	}
	return ret;
}
 
signed main()
{
//	freopen("data.in.txt","r",stdin);
//	freopen("data.out","w",stdout); 
	int n,k;
	n=read();k=read();
	for(int i=1;i<=n;i++)
	{
		int x=read();
		sum[i]=sum[i-1]^x;
	}
	for(int i=0;i<=n;i++)ins(sum[i]);
	for(int i=0;i<=n;i++) 
	{
		d[i].w=find(sum[i],1);
		d[i].id=i;d[i].num=1;
		q.push(d[i]);
	}
	k<<=1;
	for(int i=1;i<=k;i++)
	{
		Node tmp=q.top();
		q.pop();
		ans+=tmp.w;
		d[tmp.id].num++;
		d[tmp.id].w=find(sum[tmp.id],d[tmp.id].num);
		q.push(d[tmp.id]);
	}
	cout<<(ans>>1ll)<<'\n';
	return 0;
}

标签:队列,Trie,题解,ret,son,int,异或,now,联考
来源: https://www.cnblogs.com/xrkforces/p/luogu-P5283.html