【Codeforces 888G】Xor-MST | 最小异或生成树、字典树、分治

题目链接

题目大意:

给出n个点的点值,其中点u的点值为a[u],v的点值为a[v],连接u、v的边的权值是a[u]^a[v],求最小

异或生成树。

解题思路:

求一些数的最大或最小异或值,我们可以很轻松的想到用字典树,如果不明白,那先需要学习一下这

个题 最大异或对 ,通过这个题我们可以发现在字典

树中两个数的最近公共祖先越近,这两个数的异或值也就越小,请看下图:
【Codeforces 888G】Xor-MST | 最小异或生成树、字典树、分治_第1张图片

假如求的是最小异或值,6的目标毫无疑问是7,6与7的公共祖先最近,反之,7也一样。但是在求2的

目标时,就有些小问题,2在这棵树的位数是两位,6和7都是三位,位数不同,怎么异或呢?

解决的方案就是你在建树的时候,每个数建32位,这样所有int范围内的数的位数就相同了,无非就是

前面加上若干个前导0,0^0=0,问题也就顺理成章地解决了,最后可以发现2的目标是6。

下面再来文字解释一下在一棵字典树中寻找一个数的最小异或值问题,尽量保证高位相同,某一位实

在没有与之相同的位的数时,就退而求其次,那就走个分叉,由此看来,就是找的与其公共祖先最近

的那个数。上述也可以理解成是贪心的做法。

言归正传,回归到本题来说,对于树中的某一个节点p来说,p的左子树内部异或绝对优于p的左子树中

任意一个点异或p的右子树的任意一个点,右子树也是一样。在求以p为根节点的最小异或生成树生成

树时,明显就是左子树内部求+右子树内部求+左右子树中的某两点互相连接成树。

这里明显就是分治的思想,前两项可以利用递归求得,最后一项可以用暴力枚举求得,以右子树为标

准,枚举左子树的所有数与右子树的数异或,求一个min,当然以左子树为标准也可。这里和最大异或

对的做法就差不多了。

到这里还有一个问题,就是怎样知道某一个节点下的左右子树中分别有哪些数?

这里的解决方案就是首先对所有的点值排个序,这样在建树的时候它的顺序是从左到右,用两个数

组,l[i]表示数的二进制表示中包含i这个节点在序列中下标的左端点,r[i]就是右端点,不懂的话可以在

看看代码中的解释。

Code:

#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 10000010;
const int mod = 1e9+9;
typedef long long ll;
typedef pair<int,int> PII;
ll n,m,k;
int idx;
ll a[maxn];
int l[maxn],r[maxn];
int tr[maxn][2];
inline bool read(ll &num)
{char in;bool IsN=false;
in=getchar();if(in==EOF) return false;while(in!='-'&&(in<'0'||in>'9')) in=getchar();if(in=='-'){ IsN=true;num=0;}else num=in-'0';while(in=getchar(),in>='0'&&in<='9'){num*=10,num+=in-'0';}if(IsN) num=-num;return true;}
void Insert(ll x,int id){
    int p=0;
    for(int i=32;i>=0;i--){
        int u=x>>i&1;
        if(!tr[p][u]) tr[p][u] = ++idx;
        p=tr[p][u];
        if(!l[p]) l[p] = id; ///id就是这个数在序列中的位置,只要插入新的数,右端点可以直接被更新,左端点需要判断一下
                            ///是不是第一个插入的数,排序的目的就是保证这个l[p]与r[p]表示的这段区间是连续的。
        r[p] = id;
    }
}
ll AC(int p,int pos,ll x){
    ll res=0;
    for(int i=pos;i>=0;i--){
        int u=x>>i&1;
        if(tr[p][u]) p = tr[p][u];
        else{
            p = tr[p][!u];
            res += (1<<i);
        }
    }
    return res;
}
ll query(int p,int pos){
    if(tr[p][0] && tr[p][1]){
        int x = tr[p][0],y = tr[p][1];
        ll minn=1e17;
        for(int i=l[x];i<=r[x];i++)
            minn=min(minn,AC(y,pos-1,a[i])+(1<<pos));
            return minn+query(tr[p][0],pos-1)+query(tr[p][1],pos-1);///分治
    }
    else if(tr[p][0]) return query(tr[p][0],pos-1);
    else if(tr[p][1]) return query(tr[p][1],pos-1);
    return 0;
}
int main(){
    read(n);
    for(int i=1;i<=n;i++) read(a[i]);
    sort(a+1,a+1+n);
    for(int i=1;i<=n;i++) Insert(a[i],i);
    printf("%lld\n",query(0,32));
    return 0;
}

人生,要有自己的价值。一个人如若不能使自己的人生辉煌,但也没有理由使它黯淡;人生可以平

凡,但不可以庸俗、堕落;人生不在乎掠取多少,而在于追求过程的完美与卓越!

你可能感兴趣的:(Codeforces题解,最小生成树,字典树)