[UOJ176]新年的繁荣

\[\newcommand\siz[1]{{{\Large|}#1{\Large|}}} \]

壹、题目描述 ¶

传送门 to UOJ.

贰、题解 ¶

称之为 \(\rm and\)\(\rm MST\)?显然我们可以使用 \(\rm Kruskal\) 或者 \(\rm Prim\) 骗得价值不菲的分数,但是,同样是 \(\rm MST\) 算法,\(\rm Boruvka\) 在这方面,居然可以直接获得 \(100pts\)?不知道 \(\rm Boruvka\) 就自行 \(\text{OI Wiki}\) 了。

此处主要说明如何找一个点到其连通块之外的点的最大 \(\rm and\) 值了。

显然需要维护一棵 \(\rm trie\) 树,对于树上每个节点,维护加入到其子树中的点所属的连通块的最大值、最小值,如果我们想找一个属于 \(i\) 集合的点到其他集合点的 \(\rm and\) 最大值,我们走的时候只需要看看我们的目标点是否 \(\max_{id}=\min_{id}=i\),如果是,那么就走不成了。

但是还有一个问题 —— 如果 \(a_i\) 该位为 \(1\) 还好,我们只需要往 \(1\) 方向走,但是 \(a_i\) 该位为 \(0\) 时,往左往右怎么知道哪边更优?该处使用一个很强的操作,因为我们并不涉及类似计数问题的东西,即最后答案与树的形态无关,那么我们可以考虑将每个点的右子树复制一份合并到左子树上面去,这样往左子树走时就相当于左右同时走了。

时间复杂度 \(\mathcal O(nm\log n)\).

叁、参考代码 ¶

理论可过,但是会 \(\color{yellow}{\text{MLE}}\),这我就没办法了......

#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
#include<vector>
#include<set>
#include<iostream>
#include<cstdlib>
using namespace std;

// #define NDEBUG
#include<cassert>

namespace Elaina{
    #define rep(i, l, r) for(int i=(l), i##_end_=(r); i<=i##_end_; ++i)
    #define drep(i, l, r) for(int i=(l), i##_end_=(r); i>=i##_end_; --i)
    #define fi first
    #define se second
    #define mp(a, b) make_pair(a, b)
    #define Endl putchar('\n')
    #define mmset(a, b) memset(a, b, sizeof a)
    // #define int long long
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int, int> pii;
    typedef pair<ll, ll> pll;
    template<class T>inline T fab(T x){ return x<0? -x: x; }
    template<class T>inline void getmin(T& x, const T rhs){ x=min(x, rhs); }
    template<class T>inline void getmax(T& x, const T rhs){ x=max(x, rhs); }
    template<class T>inline T readin(T x){
        x=0; int f=0; char c;
        while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
        for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
        return f? -x: x;
    }
    template<class T>inline void writc(T x, char s='\n'){
        static int fwri_sta[1005], fwri_ed=0;
        if(x<0) putchar('-'), x=-x;
        do fwri_sta[++fwri_ed]=x%10, x/=10; while(x);
        while(putchar(fwri_sta[fwri_ed--]^48), fwri_ed);
        putchar(s);
    }
}
using namespace Elaina;

const int maxn=1e5;
const int inf=0x3f3f3f3f;

vector<int>a;
vector< vector<int> >compo;
int n, m;

inline void input(){
    n=readin(1), m=readin(1);
    a.resize(n);
    for(int i=0; i<n; ++i)
        a[i]=readin(1);
}

struct edge{
    int u, v, w;
    inline bool operator <(const edge rhs) const{
        return w<rhs.w;
    }
};

namespace ufs{
    vector<int>fa;
    int find(int u){ return fa[u]==u? u: fa[u]=find(fa[u]); } 
    inline bool merge(int u, int v){
        u=find(u), v=find(v);
        if(u==v) return false;
        if(compo[u]<compo[v]) swap(u, v);
        for(int x: compo[v]){
            compo[u].push_back(x);
            fa[x]=u;
        }
        compo[v].clear();
        return true;
    }
    inline void build(){
        fa.resize(n);
        rep(i, 0, n-1) fa[i]=i;
    }
}

namespace saya{
    int trie[maxn*10+5][2], ncnt;
    int mx[maxn*10+5], mn[maxn*10+5];
    inline void clear(){ ncnt=0; }
    inline int newnode(){
        ++ncnt;
        mx[ncnt]=-1, mn[ncnt]=n;
        trie[ncnt][0]=trie[ncnt][1]=0;
        return ncnt;
    }
    inline void update(int i, int c){
        getmax(mx[i], c), getmin(mn[i], c);
    }
    inline void insert(int x, int c){
        int cur=1, to; update(1, c);
        for(int j=m-1; j>=0; --j){
            to=(x>>j)&1;
            if(!trie[cur][to])
                trie[cur][to]=newnode();
            cur=trie[cur][to];
            update(cur, c);
        }
    }
    /** @warning must ensure the shape of the subtre @p v */
    int combine(int u, int v){
        if(!v) return u;
        if(!u) u=newnode();
        getmin(mn[u], mn[v]), getmax(mx[u], mx[v]);
        trie[u][0]=combine(trie[u][0], trie[v][0]);
        trie[u][1]=combine(trie[u][1], trie[v][1]);
        return u;
    }
    void dfs(int u, int dep){
        if(dep<0) return;
        if(trie[u][0]) dfs(trie[u][0], dep-1);
        if(trie[u][1]) dfs(trie[u][1], dep-1);
        trie[u][0]=combine(trie[u][0], trie[u][1]);
    }
    inline void build(){
        clear(); newnode(); // to clear the root which id is 1
        for(int i=0; i<n; ++i)
            insert(a[i], ufs::find(i));
        dfs(1, m-1);
    }
    inline pii query(int x, int c){
        int cur=1, to, ret=0;
        for(int j=m-1; j>=0; --j){
            to=(x>>j)&1;
            if(to){
                if(trie[cur][1] && (mn[trie[cur][1]]!=c || mx[trie[cur][1]]!=c))
                    ret^=(1<<j), cur=trie[cur][1];
                else cur=trie[cur][0];
            }
            else cur=trie[cur][0];
        }
        return {c==mn[cur]? mx[cur]: mn[cur], ret};
    }
}

signed main(){
    input();
    compo.resize(n);
    rep(i, 0, n-1) compo[i]=vector<int>(1, i);
    ufs::build();
    int cnt=n; ll ans=0;
    while(cnt>1){
        saya::build();
        vector<edge>Es; edge mx;
        for(auto cur: compo) if(!cur.empty()){
            mx={-1, -1, -1};
            for(int x: cur){
                pii ret=saya::query(a[x], ufs::find(x));
                mx=max(mx, {x, ret.fi, ret.se});
            }
            Es.push_back(mx);
        }
        for(auto e: Es) if(ufs::merge(e.u, e.v))
            --cnt, ans+=e.w;
    }
    writc(ans);
    return 0;
}

肆、关键之处 ¶

此题将右子树合并到左子树上的基础条件是,所求与 \(\rm trie\) 树的形态正确与否无关。

posted @ 2021-07-19 21:05  Arextre  阅读(227)  评论(0编辑  收藏  举报