51Nod 1601 完全图的最小生成树计数-异或为边权

题目链接:http://www.51nod.com/Challenge/Problem.html#problemId=1601
题目大意:
51Nod 1601 完全图的最小生成树计数-异或为边权_第1张图片
51Nod 1601 完全图的最小生成树计数-异或为边权_第2张图片
思路:
这是一张完全图,并且边的权值是由点的权值xor得到的,所以我们考虑贪心的思想,考虑kruskal的过程选取最小的边把两个连通块合并,所以我们可以模仿kruskal的过程,倒着做kruskal,设定当前的最高位为d,我们把点集分为两个集合,s集合代表d位为1的点,t集合代表d位为0的点,就是st两个连通块,考虑这两个连通块的连接,把t连通块建出一棵trie树,然后枚举s集合中的点,去查找最小边,然后统计最小边的数量,递归解决st两个连通块,最后统计方案数的时候就是乘法原理…

为什么按照每一位的01来划分集合?我们考虑现在把s拆成两个连通块,这样一共有三个连通块,如果按照贪心的思想,一定是先连接s的连通块,因为最高位一定是0,这样边比较小…

需要注意的细节就是如果有很多相同的点,并且这张子图是完全图,那么这就是一个完全图生成树计数的问题,根据prufer可以得出点数为n的完全图生成树计数为n^(n−2)…证明请见:http://www.matrix67.com/blog/archives/682

#include
#define LL long long
using namespace std;
const LL mod = 1e9+7;

int a[100005];
int s[100005];
int t[100005];
struct Trie{
    int cut, nex[2];
}tr[3000005];
int tot=0;
LL sum=0, anscut=1;

void init(){
    for(int i=0; i<=tot; i++){
        tr[i].cut=0; tr[i].nex[0]=tr[i].nex[1]=0;
    }
}

inline int power(int x,int y){
    int res=1;
    while(y){
        if(y&1) res=1LL*res*x%mod;
        x=1LL*x*x%mod,y>>=1;
    }
    return res;
}

void inset(int x){//插入字典树
    int p=0, y;
    for(int i=30; i>=0; i--){
        y=(x>>i)&1;
        if(!tr[p].nex[y]){
            tr[p].nex[y]=++tot;
        }
        p=tr[p].nex[y];
    }
    tr[p].cut++;
}

pair<LL, LL> fmin(int x){//find 最小值

    LL ans=0, p=0, y;
    for(int i=30; i>=0; i--){
        y=(x>>i)&1;
        if(tr[p].nex[y]){
            p=tr[p].nex[y];
        }
        else{
            ans+=1<<i;
            p=tr[p].nex[y^1];
        }
    }
    return {ans, tr[p].cut};
}

void solve(int l, int r, int pos){//按pos位分解子树

    if(l>=r) return;
    if(pos<0){//大小为x的块,有x^(x-2)的建图方案
        if(r-l+1>=2){
            anscut=1ll*anscut*power(r-l+1, r-l-1)%mod;
        }
        return ;
    }

    //按这一位为0/1分离
    s[0]=0;t[0]=0;
    for(int i=l; i<=r; i++){
        if(a[i]&(1<<pos)){
            s[++s[0]]=a[i];
        }
        else{
            t[++t[0]]=a[i];
        }
    }
    for(int i=1; i<=t[0]; i++){
        a[l+i-1]=t[i];
    }
    for(int i=1; i<=s[0]; i++){
        a[l+t[0]+i-1]=s[i];
    }

    //子典树
    init();
    tot=0;
    for(int i=1; i<=s[0]; i++){
        inset(s[i]);
    }
    pair<LL, LL> Tem;
    LL ans=1ll<<60, cut=0;
    //子典树存在 s[0]!=0
    for(int i=1; i<=t[0]&&s[0]; i++){
        Tem=fmin(t[i]);
        if(Tem.first<ans){
            ans=Tem.first; cut=Tem.second;
        }
        else if(Tem.first==ans){
            cut+=Tem.second;
        }
    }
    if(ans!=1ll<<60){
        sum+=ans; anscut=1ll*cut*anscut%mod;
    }
    int tcut=t[0];
    solve(l, l+tcut-1, pos-1); solve(l+tcut, r, pos-1);//分治

}

int main(){

    int n;scanf("%d", &n);
    for(int i=1; i<=n; i++){
        scanf("%d", &a[i]);
    }
    solve(1, n, 30);
    printf("%lld\n%d\n",sum,anscut);

    return 0;
}

你可能感兴趣的:(字典树,分治)