题目
解法
容易想到将所有权值都放在 ( ext{trie}) 树上,就像这样(图是我嫖的):
原问题转化为配对 (n-1) 对叶子节点,每一对叶子节点的贡献是它们的异或。需要最小化总贡献。
考虑合并两棵子树,需要分别在子树选择一个叶子节点进行配对,假设为 ((x,y))。((x,y)) 的 ( m lca) 肯定深度越深越优,因为 (2^k>sum_{i=0}^{k-1}2^i)。
这样,我们不妨将有两棵子树的点全部选为 ( m lca),因为它肯定是合并这两棵子树最优的点。
如何合并?关于两棵子树内寻找某一条件的二元组的问题,我们用启发式合并。在每个节点存下子树的所有叶子权值。
由于启发式合并套了个查询函数,总时间复杂度 (mathcal O(nlog nlog 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),'
');
return 0;
}