51NOD 1601 完全圖的最小生成樹計數 Trie

更好的瀏覽體驗 Press Here

Problem

傳送門 >ω<

題目大意:
n 個點,每個點權值爲 a[i] ,兩個點連邊費用爲 a[i]  xor  a[j] ,問 最小生成樹的邊權和 and 方案數

Solution

最小生成樹麼,一般使用 Kruskal 算法
但是在這裏,由於邊數達到了 1e10 的級別,顯然是不能直接排序的

但是隻要抓住 Kruskal 的精髓:邊權從低到高合併
那麼考慮一下如何找出邊權最低的點對:按照二進制位從低到高合併

每個點的二進制狀態我們可以用一顆 Trie 來維護,顯然其 LCA 越深其異或值越小
所以將 Trie 從低到高合併的過程是這樣的

Kruskal(T):
    T 右子樹非空 Kruskal(T 右子樹)
    T 左子樹非空 Kruskal(T 左子樹)
    //這樣就形成了兩個聯通塊,只需要在兩個塊內選擇一條邊相連即可
    若左右子樹均非空
        在左右子樹中找出異或值最小點對
        計算方案數
    最小生成樹 邊權+ 方案數*

找異或值最小點對過程如下

cal(x , y):
    x 的左子樹非空 && y 的左子樹非空 cal(x 的左子樹 , y 的左子樹)
    x 的右子樹非空 && y 的右子樹非空 cal(x 的右子樹 , y 的右子樹)
    若沒有相同的子樹(左左/右右)
    x 的左子樹非空 cal(x 的左子樹 , y 的右子樹)
    x 的右子樹非空 cal(x 的右子樹 , y 的左子樹)
    返回最小異或值,方案數

這樣利用其左右子樹獨立的性質就可以很簡單的解決這道題

分析

這個題目的複雜度很多人認爲就是 O(nlog2n)
但是真的是這樣麼

一棵滿二叉樹能夠使得每次計算最小點對時訪問到所有兒子節點

滿二叉樹一共有 logn 層,這是由節點個數所限制的,而鏈的長度爲 logmax(a[i])

所以當滿二叉樹的所有葉子節點下方都掛着一條長爲 logmax(a[i])logn 的鏈時,達到最大複雜度

這時,滿二叉樹的每個節點被訪問 dep[x] 次,鏈上每個節點被訪問 logn

總複雜度爲 O(nlogn(loga[i]n)+nlogn)

代碼

#include <bits/stdc++.h>
using namespace std;
const int N = 1000010;
const int mod = 1000000007;
typedef long long ll;
int s[N << 5][2];
int c[N << 5];
int n , cnt = 1 , m1 , c1;
long long ans1 = 1 , ans2;
int read() {
    int ans = 0 , flag = 1;
    char ch = getchar();
    while(ch > '9' || ch < '0') {if(ch == '-') flag = -1; ch = getchar();}
    while(ch <= '9' && ch >= '0') {ans = ans * 10 + ch - '0'; ch = getchar();}
    return ans * flag;
}
int qpow(int a , int b) {
    int ans = 1;
    while(b) {
        if(b & 1) ans = 1ll * ans * a % mod;
        a = 1ll * a * a % mod;
        b >>= 1;
    }
    return ans;
}
void insert(int x) {
    int now = 1;
    for(int i = 29 ; ~ i ; -- i) {
        if(!s[now][(x >> i) & 1]) s[now][(x >> i) & 1] = ++ cnt;
        now = s[now][(x >> i) & 1];
    }
    ++ c[now];
}
void get_min(int x , int y , int d , int v = 0) {
    if(d < 0) {
        if(v < m1) {m1 = v; c1 = 1ll * c[x] * c[y] % mod;}
        else if(v == m1) {c1 = (1ll * c1 + 1ll * c[x] * c[y] % mod) % mod;}
    }
    if(s[x][0] && s[y][0]) {
        get_min(s[x][0] , s[y][0] , d - 1 , v);
        if(s[x][1] && s[y][1]) get_min(s[x][1] , s[y][1] , d - 1 , v);
    }
    else if(s[x][1] && s[y][1]) get_min(s[x][1] , s[y][1] , d - 1 , v);
    else {
        if(s[x][0]) get_min(s[x][0] , s[y][1] , d - 1 , v + (1 << d));
        if(s[x][1]) get_min(s[x][1] , s[y][0] , d - 1 , v + (1 << d));
    }
}
void solve(int x , int d) {
    if(d < 0) {
        if(c[x] > 2) ans1 = ans1 * qpow(c[x] , c[x] - 2) % mod;
        return;
    }
    if(!s[x][0]) solve(s[x][1] , d - 1);
    else if(!s[x][1]) solve(s[x][0] , d - 1);
    else {
        solve(s[x][0] , d - 1);
        solve(s[x][1] , d - 1);
        m1 = 1 << 30 , c1 = 0;
        get_min(s[x][0] , s[x][1] , d - 1);
        ans2 = ans2 + (1ll << d) + m1;
        ans1 = ans1 * c1 % mod;
    }
}
int main() {
    n = read();
    for(int i = 1 ; i <= n ; ++ i) insert(read());
    solve(1 , 29);
    printf("%lld\n%lld\n", ans2 , ans1);
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章