codeforces 888G.Xor-MST(01字典樹+貪心+最小異或生成樹)
阿新 • • 發佈:2018-12-24
給你n個點,每條邊的邊權是兩個點的異或和,問你形成最小生成樹,需要的代價是多少。(
n≤200000,ai≤230 )
思路:把數都插到字典樹裡面,然後考慮兩個數的合併,最小代價的話,應該是儘可能相同的多,所以可以看做是兩個子樹的合併,那麼插的時候記錄一個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;
}