题目
解法
容易想到将所有权值都放在 \(\text{trie}\) 树上,就像这样(图是我嫖的):
首先选深度越深的点肯定越优,因为 \(2^k>\sum_{i=0}^{k-1}2^i\)。
这样,我们不妨将有两棵子树的点全部选择,因为它肯定是合并这两棵子树最优的点。
如何合并?关于两棵子树内寻找某一条件的二元组的问题,我们用启发式合并。在每个节点存下有什么权值经过了它即可。
由于启发式合并套了个查询函数,总时间复杂度 \(\mathcal O(n\log n\log a)\)。
另外我真的觉得题解区的 dfs
和奇奇怪怪的优化时间复杂度不太对,可为什么都这么快?
代码
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define print(x,y) write(x),putchar(y)
template <class T> inline T read(const T sample) {
T x=0; int f=1; char s;
while((s=getchar())>'9'||s<'0') if(s=='-') f=-1;
while(s>='0'&&s<='9') x=(x<<1)+(x<<3)+(s^48),s=getchar();
return x*f;
}
template <class T> inline void write(const T x) {
if(x<0) return (void) (putchar('-'),write(-x));
if(x>9) write(x/10);
putchar(x%10^48);
}
typedef long long ll;
const int maxn=2e5+5;
int n,a[maxn],idx;
int to[maxn*31][2];
vector <int> g[maxn*31];
void ins(int v) {
int p=0;
for(int i=30;i>=0;--i) {
bool d=(v>>i&1);
if(!to[p][d]) to[p][d]=++idx;
p=to[p][d];
g[p].push_back(v);
}
}
ll ask(int o,int v,int dep) {
ll ans=0;
for(int i=dep;i>=0;--i) {
bool d=(v>>i&1);
if(to[o][d]) o=to[o][d];
else {
ans^=(1<<i);
o=to[o][d^1];
}
}
return ans;
}
ll query(int o,int dep) {
if(dep<0) return 0;
ll ans=0;
if(to[o][0]) ans+=query(to[o][0],dep-1);
if(to[o][1]) ans+=query(to[o][1],dep-1);
if(!to[o][0] or !to[o][1]) return ans;
bool d=(g[to[o][0]].size()>g[to[o][1]].size());
ll mn=1e18;
for(int i=0;i<g[to[o][d]].size();++i)
mn=min(mn,ask(to[o][d^1],g[to[o][d]][i],dep-1));
return mn+ans+(1<<dep);
}
int main() {
n=read(9);
for(int i=1;i<=n;++i)
a[i]=read(9);
sort(a+1,a+n+1);
n=unique(a+1,a+n+1)-a-1;
for(int i=1;i<=n;++i)
ins(a[i]);
print(query(0,30),'\n');
return 0;
}