您的位置:首页 > 其它

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

2018-02-11 18:57 295 查看
G. Xor-MSTtime limit per test:2 secondsmemory limit per test:256 megabytesinput:standard inputoutput:standard outputYou are given a complete undirected graph with 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.InputThe first line contains n (1 ≤ n ≤ 200000) — the number of vertices in the graph.The second line contains n integers a1, a2, ..., an (0 ≤ ai < 230) — the numbers assigned to the vertices.OutputPrint one number — the weight of the minimum spanning tree in the graph.ExamplesInput
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
;
int f
,w
,n; LL ans;
bool v
;

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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: