CF888G Xor-MST 生成樹、分治、Trie樹合並


傳送門


第一次接觸到Boruvka求最小生成樹

它的原版本是:初始每一個點構成一個連通塊,每一次找到每一個連通塊到其他的連通塊權值最短的邊,然后合並這兩個連通塊。因為每一次連通塊個數至少減半,所以復雜度是\(O((n+m)logn)\)

雖然它的原版本用途不多,但是思想可以涵蓋很多其他題目,比如這道題

可以想到一個做法:將所有權值插入一個\(Trie\)里,在每一個葉子節點維護到達這個節點的數的編號。像上面那樣維護若干連通塊,每一次計算權值最小的邊時,將當前連通塊中所有權值從Trie中刪去,然后對於連通塊中的每個權值在\(Trie\)上找到異或和最小的數字和編號,最后連邊、恢復原來的\(Trie\)

復雜度\(O(nlog^2n)\),但常數太大,哪怕在\(CF\)的神機下大數據也會直接淪陷QAQ

正着不行,就反着考慮。設能夠產生貢獻的二進制最高位為\(k\),即對於所有數來說,存在第\(k\)位為\(0\)的數,也存在第\(k\)位為\(1\)的數,且對於\(>k\)的數均不滿足這一條件。那么最優的連邊方法顯然是:這一位為\(1\)的數之間連成一個生成樹,這一位為\(0\)的數之間連成一個生成樹,然后在這兩個點集之間連一條邊。可以發現這個問題變成了兩個子問題,且對於這兩個子問題的\(k\)一定會小於當前問題的\(k\),所以可以直接遞歸下去。

考慮如何計算當前層連的邊的貢獻。不妨讓每一層遞歸結束時把當前層所有權值對應的\(Trie\)樹建好傳給上面一層,那么每一層可以獲得這一位為\(1\)的所有數的\(Trie\)和這一位為\(0\)的所有數的\(Trie\)。將點數較少的點集中所有點的權值放在點數較多的點集對應的\(Trie\)上跑最小值,就可以得到當前層連邊的權值大小。計算完貢獻后將兩個\(Trie\)用類似線段樹合並的方式合並,可以有效避免\(MLE\)

總復雜度仍然是\(O(nlog^2n)\)但跑得快了不少。

#include<iostream>
#include<cstdio>
#include<cctype>
#include<algorithm>
#include<cstring>
#include<iomanip>
#include<vector>
#include<set>
//This code is written by Itst
using namespace std;

inline int read(){
    int a = 0;
    char c = getchar();
    while(!isdigit(c))
        c = getchar();
    while(isdigit(c)){
        a = a * 10 + c - 48;
        c = getchar();
    }
    return a;
}

const int MAXN = 2e5 + 3;

struct node{
    node *ch[2];
    node(){ch[0] = ch[1] = NULL;}
};

struct Trie{
    node *rt = new node;
    
    void ins(int x){
        node *cur = rt;
        for(int i = 29 ; i >= 0 ; --i){
            if(cur->ch[(bool)(x & (1 << i))] == NULL)
                cur->ch[(bool)(x & (1 << i))] = new node;
            cur = cur->ch[(bool)(x & (1 << i))];
        }
    }
    
    int query(int x){
        int ans = 0;
        node *cur = rt;
        for(int i = 29 ; i >= 0 ; --i){
            bool f = x & (1 << i);
            if(cur->ch[f] != NULL)
                cur = cur->ch[f];
            else{
                cur = cur->ch[!f];
                ans += 1 << i;
            }
        }
        return ans;
    }
};
int N;
long long sum;
vector < int > val;

node* merge(node *A , node *B){
    if(A == NULL) return B;
    if(B == NULL) return A;
    A->ch[0] = merge(A->ch[0] , B->ch[0]);
    A->ch[1] = merge(A->ch[1] , B->ch[1]);
    return A;
}

Trie merge(Trie A , Trie B){
    A.rt = merge(A.rt , B.rt);
    return A;
}

Trie solve(vector < int > val , int now){
    if(val.empty()) return Trie();
    if(now < 0){
        Trie t;
        t.ins(val[0]);
        return t;
    }
    vector < int > lft , rht;
    for(auto t : val)
        t & (1 << now) ? rht.push_back(t) : lft.push_back(t);
    Trie L = solve(lft , now - 1) , R = solve(rht , now - 1);
    if(lft.size() < rht.size()){
        swap(lft , rht);
        swap(L , R);
    }
    int minN = 2e9;
    for(auto t : rht) minN = min(minN , L.query(t));
    if(!rht.empty()) sum += minN;
    return merge(L , R);
}

int main(){
#ifndef ONLINE_JUDGE
    freopen("in","r",stdin);
    //freopen("out","w",stdout);
#endif
    N = read();
    for(int i = 1 ; i <= N ; ++i)
        val.push_back(read());
    sort(val.begin() , val.end());
    solve(val , 29);
    cout << sum;
    return 0;
}

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com