1. 程式人生 > 實用技巧 >2020牛客多校第五場B-Graph

2020牛客多校第五場B-Graph

題意

給一棵樹,每條邊有邊權。可以任意加邊和刪邊,但要滿足任何時刻圖連通,而且任何一個環的邊權異或和為0。求操作後最小權值和

題解

任意兩點間連邊的權值是固定的,可以預處理給每個點賦值點權,兩點間的邊權就是點權的異或,點權直接dfs一遍取鏈上的異或和即可。

然後就是異或最小生成樹模板

對於異或最小生成樹,有Boruvka演算法,先對於每個點,選擇在所有與之相連的邊中,權值最小的邊,並將這條邊加入到最小生成樹中。顯然這樣連出來的邊會形成一個森林,並且連邊後連通塊個數至少減半。然後我們將每個連通塊再看成一個點,重複以上演算法即可。時間複雜度O(mlogn)。

對於此題,我們把所有點權扔到Trie裡,對於每一層,有兩種情況,

  1. 一種全為0,或者全為1,那麼這一位無需考慮,不會對答案產生貢獻。

  2. 一部分為0,一部分為1,那麼兩組之間要有一條邊把這兩個組相連,在字典樹上找到異或值最小的作為連線兩個集合的邊,加入答案。對兩組分別遞迴求解。

程式碼

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct READ {
    inline char read() {
    #ifdef _WIN32
        return getchar();
    #endif
        static const int IN_LEN = 1 << 18 | 1;
        static char buf[IN_LEN], *s, *t;
        return (s == t) && (t = (s = buf) + fread(buf, 1, IN_LEN, stdin)), s == t ? -1 : *s++;
    }
    template <typename _Tp> inline READ & operator >> (_Tp&x) {
        static char c11, boo;
        for(c11 = read(),boo = 0; !isdigit(c11); c11 = read()) {
            if(c11 == -1) return *this;
            boo |= c11 == '-';
        }
        for(x = 0; isdigit(c11); c11 = read()) x = x * 10 + (c11 ^ '0');
        boo && (x = -x);
        return *this;
    }
} in;

const int N = 1e5 + 50;
int a[N];
struct node {
    int v,  w;
    node(int v = 0, int w = 0): v(v), w(w) {}
};
vector<node> G[N];
int cnt = 0;
void dfs(int u, int fa) {
    for (auto nx : G[u]) {
        if (nx.v == fa) continue;
        a[nx.v] = a[u] ^ nx.w;
        dfs(nx.v, u);
    }
}
int trie[N << 5][2];
int tot = 0;
void ins(int x) {
    int rt = 0;
    for (int i = 30; i >= 0; i--) {
        int now = (x >> i) & 1;
        if (!trie[rt][now]) trie[rt][now] = ++tot;
        rt = trie[rt][now];
    }
}
int qry(int x) {
    int ans = 0, rt = 0;
    for (int i = 30; i >= 0; i--) {
        int now = (x >> i) & 1;
        if (trie[rt][now]) rt = trie[rt][now];
        else {
            rt = trie[rt][now ^ 1];
            ans |= (1 << i);
        }
    }
    return ans;
}
ll ans = 0;
void calc(int l, int r, int dep) {
    if (dep < 0 || l >= r) return;
    int mid = l - 1;
    while (mid < r && !((a[mid + 1] >> dep) & 1)) mid++;
    calc(l, mid, dep - 1);
    calc(mid + 1, r, dep - 1);
    if (mid == l - 1 || mid == r) return;
    tot = 0;
    for (int i = l; i <= mid; i++) ins(a[i]);
    int res = 0x7fffffff;
    for (int i = mid + 1; i <= r; i++) res = min(res, qry(a[i]));
    ans += res;
    for (int i = 0; i <= tot; i++) trie[i][0] = trie[i][1] = 0;
}
int main() {
    int n; in >> n;
    for (int i = 1; i < n; i++) {
        int u, v; ll w;
        in >> u >> v >> w;
        u++; v++;
        G[u].push_back(node(v, w));
        G[v].push_back(node(u, w));
    }
    dfs(1, 0);
    sort(a + 1, a + n + 1);
    calc(1, n, 30);
    printf("%lld\n", ans);
    return 0;
}