trie树的应用;
链接:https://ac.nowcoder.com/acm/contest/920/B
来源:牛客网
定义一张图的生成链是原图的一棵生成树,且这棵树退化成一条链。我们称一条生成链是原图的最小生成链,当且仅当该最小生成链中边权最大的边是原图的所有生成链最大边的边权中最小的。
现有一个 n 个点的完全图,点编号为1 到 n。另给出一个长度为n 的序列 ai,完全图中第 i 个点与第j 个点间的边的边权为 ai⊕aj,其中 ⊕ 表示按位异或运算。
请您找出该完全图的最小生成链。由于答案可能很多,您只需输出这条最小生成链中边权最大的边的边权即可。
这道题可以想到一个性质,就是在比较两个数的大小时,如果前面位置的数相同,从第一个不同的数开始比较,那么位置大的数大的这个数就大;
就是从高位比较;
二进制数也有这个性质;
这个题可以用最小生成树来写暴力(部分分),就是可以证明最小生成树上最大的边等于最小生成链上最大的边;
我也不会证,我也不敢问;
贴个题解
:作者:Tweetuzki
链接:https://ac.nowcoder.com/discuss/227149?type=101
来源:牛客网
证明:首先最小生成树的最大边一定不大于最小生成链的最大边,
然后按照下文的做法分为 0,1 两块后,
中间这条边就是最小生成链最大边。
如果不连上这条边,那么 0的连通块和 1的连通块无法连通。
因此这条边一定在最小生成树中,也就是说最小生成树的最大边不小于最小生成链的最大边。
于是就证明了两种的最大边相等。
就是在所有点的权值中,前面的位置数都相同,那么异或后都是零,不用管;
从第一个有0有1的位置开始(位置为i),可以把0分为一组,1分为一组;
0中连边不会超过1<<i,1也是一样;
那么只缺这两个连通块中连一条边了,这个边就是最大边;
我们希望这个交界的部分两元素异或值最小。也就是说,我们需要从这一位为 00 的元素中和这一位为 11的元素中各找出一个元素,使得这两个元素的异或值最小。
接下来这个找最小值的过程可以进行优化。这里有异或操作,很自然地能够想到 0-1 Trie。我们可以维护一棵 0-1 Trie,我们将序列中所有这一位为 0 的元素插入这棵 Trie,然后用所有这一位为 1的元素去 Trie 中查异或最小值。最后所有最小值的最小值就是答案了。
要注意特判一下所有元素都相同的情况,因为这会找不到这个最高的位满足这一位上有 0 和 1。
时间复杂度和空间复杂度都是 O(nlogai)。
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn=1e6+100; typedef long long ll; ll trie[3][maxn],cnt=1; ll n,a[maxn]; ll maxl=-1; ll ans=1e18; void insert(ll x) { int u=1; for(int i=59;i>=0;i--) { int c=(x&(1ll<<i))?1:0; if(!trie[c][u]) trie[c][u]=++cnt; u=trie[c][u]; } } ll qmin(ll x) { int u=1; ll res=0; for(int i=59;i>=0;i--) { int c=(x&(1ll<<i))?1:0; if(trie[c][u]!=0) u=trie[c][u]; else { res|=1ll<<i; u=trie[c^1][u]; } } return res; } int main() { scanf("%lld",&n); for(int i=1;i<=n;i++) { scanf("%lld",a+i); } sort(a+1,a+n+1); for(int i=59;i>=0;i--) { if((a[1]^a[n])&(1ll<<i)) { maxl=i; break; } } if(maxl==-1) { printf("0"); return 0; } for(int i=1;i<=n;i++) { if(a[i]&(1ll<<maxl)) ans=min(ans,qmin(a[i])); else insert(a[i]); } printf("%lld",ans); return 0; }