天天看点

CodeForces 888G Xor-MST(Sollin MST+Trie)

G. Xor-MST

time limit per test:2 seconds

memory limit per test:256 megabytes

input:standard input

output:standard output

n vertices. A number ai is assigned to each vertex, and the weight of an edge between vertices i and j is equal to ai xor aj.

Calculate the weight of the minimum spanning tree in this graph.

Input

n (1 ≤ n ≤ 200000) — the number of vertices in the graph.

n integers a1, a2, ..., an (0 ≤ ai < 230) — the numbers assigned to the vertices.

Output

Print one number — the weight of the minimum spanning tree in the graph.

Examples

Input

5
1 2 3 4 5      

Output

8      

Input

4
1 2 3 4      

Output

8      

           标题都已经很明显了,一个最小生成树,只不过是用xor做。

        通常来说xor的题目,要么是贪心,要么是用Trie去做,而这道题,显然要用到Trie。但是我们又如何做到求最小生成树呢?这里,我们发现求最小生成树有一个非常少见的算法——Sollin(Boruvka)算法。具体来说,就是一开始把每个点看成独立的连通分量,对于每个连通分量,每次操作找与其最近的连通分量合并,一直到只剩下一个连通分量为止。这样子最坏的情况是每次只减少一半的连通分量数目,最好的情况就直接和Prim算法相同。具体实现起来,可能不太方便,而且实践复杂度看起来不小(要枚举连通分量,以及其中的各个点,还要求最小)。

       然后,对于本题,相当于是一个模板题。由于是用xor求最小值,所以我们可以用Trie来优化求最近的连通分量的过程。对于每一个集合,我们用并查集来维护合并的过程。具体执行起来,如果用vector去存各个连通分量里面的数字,据说可能会超内促成你,所以用了排序的方法。对于每个数字,我们按照他所在的连通分量排序,这样同一个连通分量的点就在同一个连续区间里面,然后每次把一个连通分量所有点先从Trie中删去,然后再遍历连通分量所有的点,对于每个点在剩下的Trie中找最小,最小之中的最小即为解,然后合并即可。这里注意连通分量A与B合并之后,在这一次操作中就不需要再用B来与其他连通分量合并。

        具体见代码:

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define LL long long
#define N 200010

using namespace std;

struct Trie
{
    struct node{int size,ch[2];} T[N<<5];
    int tot,root;

    void init()
    {
        tot=root=1;
        memset(T,0,sizeof(T));
    }

    void ins(int x)
    {
        int o=root;
        T[o].size++;
        for(int k=30;k>=0;k--)
        {
            int c=x&(1<<k)?1:0;
            if(!T[o].ch[c]) T[o].ch[c]=++tot;
            o=T[o].ch[c];
            T[o].size++;
        }
    }

    void del(int x)
    {
        int o=root;
        T[o].size--;
        for(int k=30;k>=0;k--)
        {
            int c=x&(1<<k)?1:0;
            o=T[o].ch[c];
            T[o].size--;
        }
    }

    int query(int x)
    {
        int o=root,res=0;
        for(int k=30;k>=0;k--)
        {
            res<<=1;
            int c=x&(1<<k)?1:0;
            if (T[o].ch[c]&&T[T[o].ch[c]].size) o=T[o].ch[c],res+=c;
                                    else o=T[o].ch[c^1],res+=c^1;
        }
        return res;
    }
} Trie;

struct node{int fa,id;} p[N];
int f[N],w[N],n; LL ans;
bool v[N];

int find(int x)
{
    return f[x]==x ? x:(f[x]=find(f[x]));
}

bool cmp(node a,node b)
{
    return a.fa<b.fa;
}

bool check()                                                        //所有的点合并成同一个连通分量才算是合并完成
{
    for(int i=1;i<n;i++)
        if (find(i)!=find(i+1)) return 0;
    return 1;
}

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        scanf("%d",&w[i]);
    sort(w+1,w+1+n);
    n=unique(w+1,w+1+n)-w-1;
    for(int i=1;i<=n;i++)
    {
        p[i]=node{i,i};
        Trie.ins(w[i]); f[i]=i;
    }
    while(!check())
    {
        memset(v,0,sizeof(v));
        for(int i=1;i<=n;i++)
            p[i].fa=find(p[i].id);
        sort(p+1,p+1+n,cmp);                                        //按照所在连通分量编号排序
        int last=1,fa=p[1].fa;
        for(int i=1;i<=n;i++)
        {
            if (!v[fa]&&p[i].fa==fa) Trie.del(w[p[i].id]);            //删除具体的一个连通分量的所有点
            if (!v[fa]&&p[i+1].fa!=fa)                                    //删除完毕之后找其中最小的最小
            {
                v[fa]=1;
                int res=INF,k=0;
                for(int j=last;j<=i;j++)
                {
                    int tmp=Trie.query(w[p[j].id]);
                    if ((tmp^w[p[j].id])<res) res=tmp^w[p[j].id],k=tmp;
                }
                ans+=res;
                int pos=lower_bound(w+1,w+1+n,k)-w;
                if (fa>(pos=find(pos))) swap(fa,pos);
                for(int j=last;j<=i;j++)                                //把删掉的点重新加回Trie中去
                    Trie.ins(w[p[j].id]);
                f[find(pos)]=fa;                                            //合并
            }
            if (p[i+1].fa!=fa) fa=find(p[i+1].fa),last=i+1;
        }
    }
    cout<<ans<<endl;
    return 0;
}      

继续阅读