题解 CF282E 【Sausage Maximization】

题目传送门

既然没人发题解那就毫不客气地收下啦!

Update 2021.4.24:修复了时间复杂度错误。。

题意简述:

给你一个 \(n\) 个整数的数组,你可以把它分成三段 ( 任意一段可以为空,满足 \(l+mid+r=n\) 就行 ),求一种划分方法,使得 \(l\),\(r\) 内的所有值异或和最大。

算法分析:Trie 树

我们知道 Trie 树(又称字典树)善于解决字符串统计问题。但实际上,Trie 树同样可以用来解决部分二进制运算的问题,尤其是异或(不知道的同学请移步经典题:Hdu4825 Xor Sum)。我们可以把一个数的二进制看做一个01串,从高位开始插入字典树中,来解决相关问题。

对于本题,题目中要求最大的异或和。我们知道异或又叫“按位异或”,需要对每一个二进制位进行操作。那不就是对每一个字符进行操作了吗?用字典树呀!(但作为一道 CF 的题并不会这么水)

我们注意到题中要同时求前缀和后缀,相当于多组不同的询问。我们不可能对于每一种可能都重建字典树。枚举至少是 \(\mathcal{O}(n^2)\) 级别的,更何况还要建字典树,那还不如直接暴力呢!怎么办呢?我们要对字典树进行删除操作。(删除操作可以参考 CF706D Vasiliy's Multiset)具体的,由于二进制数存储的时候统一记录相同的位数,所以它们的结尾是可以确定的,即最大位数,所以我们无需额外记录结尾,转而用一个 \(rec\) 数组记录每个节点被几个二进制数使用过。在删除某个数时,直接对这个数对应的每一个节点的 \(rec-1\) 就可以了。

inline void del(ll x) {//删除操作 
	int p=1;//根节点 
	for(reg int i=62; i>=0; i--){//从高位开始 
		bool k=(x&(1ll<<i));
		if(!rec[p] or !trie[p][k])return;
		p=trie[p][k];
		rec[p]--;//删除 
	}
}

那么,对于本题,具体应该怎么做呢?我们可以先计算并插入所有的后缀异或和 \(bk_i\),然后从前往后扫,记录当前的前缀异或和 \(cur\) ,删除后缀异或和,然后在字典树中查找和 \(cur\) 对应的异或最大值(其实这一步就和 CF706D Vasiliy's Multiset
一样了),同时更新 \(ans\) 即可。

记得把数组开到够大!

code:

#include<bits/stdc++.h>
#define ll long long
#define reg register
#define F(i,a,b) for(reg int i=(a);i<=(b);i++)
inline ll read();
using namespace std;
const int N=1e5+10;
int n,trie[N*64][2],cnt=1,rec[N*64];
ll bk[N],a[N],ans;
inline void insert(ll x) {//插入 
	int p=1;//根节点 
	for(reg int i=62; i>=0; i--) {
		bool k=(x&(1ll<<i));
		if(!trie[p][k])trie[p][k]=++cnt;
		p=trie[p][k];
		rec[p]++;
	}
}
inline void del(ll x) {//删除操作 
	int p=1;//根节点 
	for(reg int i=62; i>=0; i--){//从高位开始 
		bool k=(x&(1ll<<i));
		if(!rec[p] or !trie[p][k])return;
		p=trie[p][k];
		rec[p]--;//删除 
	}
}
inline ll search(ll x){//模板不解释 
	int p=1;
	ll s=0;
	for(reg int i=62;i>=0;i--){
		bool k=(x&(1ll<<i));
		s<<=1ll;
		int &to=trie[p][k^1];
		if(!to or !rec[to]){
			if(rec[trie[p][k]] and trie[p][k])p=trie[p][k];
			else break;
		}
		else p=to,s|=1ll;
	}
	return s;
}
int main() {
	n=read();
	F(i,1,n)a[i]=read();
	for(reg int i=n; i>=1; i--)bk[i]=bk[i+1]^a[i],insert(bk[i]);
	//计算后缀异或和 bk ,并插入字典树 
	ll cur=0;
	ans=search(cur);//相当于查询 bk 的最大值 
	F(i,1,n){
		cur^=a[i];//维护前缀异或和 
		del(bk[i]);//删除后缀异或和 
		ans=max(ans,search(cur));//更新答案 
	}
	printf("%lld",ans);
	return 0;
}
inline ll read() {
	reg ll x=0;
	reg char c=getchar();
	while(!isdigit(c))c=getchar();
	while(isdigit(c))x=(x<<3ll)+(x<<1ll)+(c^48),c=getchar();
	return x;
}

AC

欢迎交流讨论,请点个赞哦~

posted @ 2021-06-22 18:49  Maplisky  阅读(56)  评论(0编辑  收藏  举报