题解 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;
}
欢迎交流讨论,请点个赞哦~
本文来自博客园,作者:Maplisky,转载请注明原文链接:https://www.cnblogs.com/lbh2021/p/14919893.html