题目:
个点的无向完全图,每个点有点权,边权是点权的值。问最小生成树边权和。
做法:
最小生成树,经典老题了。做法是字典树上启发式合并。
字典树上,按在左在右顺序,从左到右叶子表示的数的大小是有序的。所以我们先对点权从小到大排序,然后依次插入字典树上。这样字典树的子树下表示的数都来自原数组的一段连续区间,这便于我们处理。我们从高位到低位递归处理,先将子树和子树下的点各自分别联通,然后在这两个联通块上建一条边将其合并。这条边怎么选呢?我们可以枚举一棵子树下的所有数,在另一棵子树里查和其值最小的另一个数,这两个数建边的边权就是这个值。我们选边权最小的建边即可。而这个过程中,我们每次都枚举比较小的子树,则枚举的规模的,不会。
代码:
#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; }