题目:
个点的无向完全图,每个点有点权,边权是点权的
值。问最小生成树边权和。
做法:
最小生成树,经典老题了。做法是
字典树上启发式合并。
字典树上,按在左
在右顺序,从左到右叶子表示的数的大小是有序的。所以我们先对点权从小到大排序,然后依次插入字典树上。这样字典树的子树下表示的数都来自原数组的一段连续区间,这便于我们处理。我们从高位到低位递归处理,先将
子树和
子树下的点各自分别联通,然后在这两个联通块上建一条边将其合并。这条边怎么选呢?我们可以枚举一棵子树下的所有数,在另一棵子树里查和其
值最小的另一个数,这两个数建边的边权就是这个
值。我们选边权最小的建边即可。而这个过程中,我们每次都枚举
比较小的子树,则枚举的规模
的,不会
。
代码:
#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false), cin.tie(0)
#define debug(a) cout << #a ": " << a << endl
using namespace std;
typedef long long ll;
const int N = 2e5 + 7;
int a[N];
struct Trie{
static const int N = 5e6 + 7;
int tot, ch[N][2], val[N], in[N], out[N];
void insert(int x, int dep, int rt, int id){
if (dep == -1){
val[id] = x;
in[rt] = out[rt] = id; return;
}
int b = (x>>dep)&1;
if (!ch[rt][b]) ch[rt][b] = ++tot;
insert(x, dep-1, ch[rt][b], id);
in[rt] = ch[rt][0] ? in[ch[rt][0]] : in[ch[rt][1]];
out[rt] = ch[rt][1] ? out[ch[rt][1]] : out[ch[rt][0]];
}
int ask(int x, int dep, int rt){
if (dep == -1) return 0;
int b = (x>>dep)&1;
if (ch[rt][b]) return ask(x, dep-1, ch[rt][b]);
else return ask(x, dep-1, ch[rt][b^1])+(1<<dep);
}
ll solve(int dep, int rt){
if (dep == -1) return 0;
ll ans = 0;
int ls = ch[rt][0], rs = ch[rt][1];
if (ls) ans += solve(dep-1, ls);
if (rs) ans += solve(dep-1, rs);
if (!ls || !rs) return ans;
ll tmp = 1e17;
if (out[ls]-in[ls] <= out[rs]-in[rs]){
for (int i = in[ls]; i <= out[ls]; ++i){
tmp = min(tmp, (ll)ask(val[i], dep-1, rs)+(1<<dep));
}
}else{
for (int i = in[rs]; i <= out[rs]; ++i){
tmp = min(tmp, (ll)ask(val[i], dep-1, ls)+(1<<dep));
}
}
return ans+tmp;
}
}trie;
int main(void){
IOS;
int n; cin >> n;
for (int i = 1; i <= n; ++i) cin >> a[i];
sort(a+1,a+n+1);
for (int i = 1; i <= n; ++i) trie.insert(a[i], 30, 0, i);
cout << trie.solve(30, 0) << endl;
return 0;
}
京公网安备 11010502036488号