1. 程式人生 > >codeforces 888G.Xor-MST(01字典樹+貪心+最小異或生成樹)

codeforces 888G.Xor-MST(01字典樹+貪心+最小異或生成樹)

給你n個點,每條邊的邊權是兩個點的異或和,問你形成最小生成樹,需要的代價是多少。(n200000ai230

思路:把數都插到字典樹裡面,然後考慮兩個數的合併,最小代價的話,應該是儘可能相同的多,所以可以看做是兩個子樹的合併,那麼插的時候記錄一個siz,採取dfs,發現點root有左右兒子結點的時候,就可以合併了,合併的方式是列舉較小的子樹中的每一個數,然後跑到另一個子樹裡去查最小異或和,更新答案。

#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 200000
+ 5; typedef long long LL; long long ans, cost; struct Trie { int ch[32*maxn][2], siz[32*maxn], val[32*maxn], num[32*maxn], sz; int up = 30; init(){sz = 1;memset(ch[0], 0, sizeof(ch[0]));} void Insert(int x) { int u = 0; for(int i = up; i >= 0; i--) { int
c = ((x >> i) & 1); if(ch[u][c] == 0) { memset(ch[sz], 0, sizeof(ch[sz])); num[sz] = val[sz] = 0; ch[u][c] = sz++; } u = ch[u][c]; num[u] ++; } val[u] = x; } int
getSize(int x) { if(!ch[x][0] && !ch[x][1]) return siz[x] = 1; if(ch[x][0]) siz[x] += getSize(ch[x][0]); if(ch[x][1]) siz[x] += getSize(ch[x][1]); return siz[x]; } void dfs(int x) { if(ch[x][0]) dfs(ch[x][0]); if(ch[x][1]) dfs(ch[x][1]); if(ch[x][0] && ch[x][1]) {//要合併root = x,這個結點的左右兒子。 cost = INF; int lson = ch[x][0], rson = ch[x][1]; if(siz[lson] < siz[rson]) calc(lson, x); else calc(rson, x); ans += cost; } } void calc(int x, int pre) {//遍歷root左右子樹中較小一顆的所有數。 if(ch[x][0]) calc(ch[x][0], pre); if(ch[x][1]) calc(ch[x][1], pre); if(!ch[x][0] && !ch[x][1]) { int now = query(val[x], pre); if(cost > (val[now] ^ val[x])) { cost = (val[now] ^ val[x]); } } } int query(int x, int pre) {//pre標記之前要合併左右兒子的根結點root。 int u = 0; for(int i = up; i >= 0; i--) { int c = ((x >> i) & 1); if(ch[u][c] == 0) c = 1 - c; if(u == pre) c = 1 - c; u = ch[u][c]; } return u; } }trie; int main() { trie.init(); int n; scanf("%d", &n); for(int i = 1; i <= n; i++) { int x; scanf("%d", &x); trie.Insert(x); } trie.getSize(0); trie.dfs(0); printf("%lld\n", ans); return 0; }