题解:P9178 [COCI2022-2023#5] Diskurs
题意:对于长度为
首先,如果数列中的一个数为
于是我们可以想到,对于每个
现在我们将原问题拆解为了两个问题:全局异或和全局 popcount 最大值,考虑用 01trie 处理。
全局异或
说得可能不是很清楚,这里举个例子,如果
然后全局 popcount 可以对于每一节点维护一个
但——是,直接这样做时间复杂度显然是不对的,每一个
我们试着找一些可以优化的地方,单次修改的复杂度不是很好优化,那么我们开始优化总的修改次数。
考虑对原数组排序,排完序后,从高往低第
然而,现在对于第
看不懂的就着代码理解一下吧:
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n,m,ans[1100010],son[2200010][2],val[2200010],tot,root;
pair<int,int> a[1100010];
void pushup(int p){
if(son[p][0]&&son[p][1])val[p]=max(val[son[p][0]],val[son[p][1]]+1);//下一位为0不会对答案做贡献,下一位是1则会做出1的贡献
else if(son[p][0])val[p]=val[son[p][0]];
else if(son[p][1])val[p]=val[son[p][1]]+1;
else val[p]=0;
}
void modify(int p,int c){
if(!c){
swap(son[p][0],son[p][1]);//c每往下走一层就会减一,c==0就意味着到了指定层,交换左右儿子
pushup(p);
return;
}
if(son[p][0])modify(son[p][0],c-1);
if(son[p][1])modify(son[p][1],c-1);
pushup(p);
}
void update(int &p,int x,int len){
if(!p)p=++tot;
if(!len)return;
update(son[p][x&1],x>>1,len-1);//从最低位开始遍历
pushup(p);
}
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
scanf("%lld",&a[i].first);
a[i].second=i;
update(root,a[i].first,m);//建trie树
}
sort(a+1,a+n+1);
for(int i=1;i<=n;i++){
for(int j=0;j<m;j++)if((a[i].first>>j&1)^(a[i-1].first>>j&1))modify(1,j);//与上一个数的第j位不一样就将第j层所有节点交换左右儿子
ans[a[i].second]=val[root];
}
for(int i=1;i<=n;i++)printf("%lld ",ans[i]);
}