首页 > 代码库 > 51Nod1601 完全图的最小生成树计数

51Nod1601 完全图的最小生成树计数

传送门

我居然忘写题解啦!(记忆废)

不管怎么说,这题还算是一道好题啊……你觉得敦爷出的题会有水题么

……

这题比较容易把人误导到Boruvka算法之类的东西上去(我们机房去刚D题的人一开始大多也被误导了),但仔细思考之后是可以发现问题的特殊性质的。

听说很多人是从Kruskal算法想到这道题的做法的?好吧我并不是,那我就写写我的思考过程好了……

记得算导上有一道思考题,判断一个最小生成树算法的正确性。那个算法是这样的:把当前图的点集随意划分成两半,递归两半后选出连接两个点集的边中权值最小的一条,得到最后的最小生成树。

这个算法显然是错的,因为最终的最小生成树中可能有两条连接当前层两个点集的边。但本题有特殊性,边权都是端点点权的异或值,也许可以把这个算法改造一下用到这道题中。

考虑对一个点集求最小生成树,由于边权是端点点权的异或,因此可以把所有点按照最高位划分成两半,一半最高位为0,另一半最高位为1。这样递归两半之后只选一条连接两半的最小权边就可以得到一个生成树。可以证明它是最小生成树,并且证明起来并不难:如果某一组解中有两条边都连接了两半,由于这两条边边权的最高位一定是1,而位于两半内的边边权最高位一定是0,因此把这两条边中的一条替换成两半内的边得到的解一定比当前优。

有了正确性,算法也就成型了:每次把当前点集按最高位划分为两半后递归两半,然后若两边均非空则把任意一条连接两半的最小权边加入最小生成树即可。可能有多条边都是最小权边,显然方案数应该是每层的方案数之积。

实现的时候对所有权值建一棵01-Trie,那么选边的过程就相当于对每个点计算它的左子树和右子树之间的贡献,再dfs一遍左右子树即可。每个数最多被dfs到$\frac{32^2}2=512$次,因此复杂度为$O(512n)$。

注意一个细节:在划分过程中如果递归到了叶子节点且此处点数$>1$,则说明有多个点权值相同,显然这些点随便连就行了,那么答案就应该乘上对应点数的无向完全图生成树的数量。这个在OEIS上可以找到,通项是$n^{n-2}$。

技术分享
 1 #include<cstdio>
 2 #include<cstring>
 3 #include<cassert>
 4 #include<algorithm>
 5 using namespace std;
 6 const int maxn=100010,maxm=maxn<<6,p=1e9+7;
 7 void insert(int,int&);
 8 void solve(int,int);
 9 void dfs(int,int,int,int);
10 int qpow(int,int);
11 long long sum=0;
12 int sm[maxm]={0},ch[maxm][2]={{0}},root=0,cnt=0;
13 int n,a[maxn],x,ans=1,mn,tmp;
14 signed main(){
15     scanf("%d",&n);
16     for(int i=1;i<=n;i++){
17         scanf("%d",&x);
18         insert(30,root);
19     }
20     solve(30,root);
21     //assert(ans==1ll);
22     printf("%lld\n%d",sum,ans);
23     return 0;
24 }
25 void insert(int k,int &rt){
26     if(!rt)rt=++cnt;
27     sm[rt]++;
28     if(k==-1)return;
29     insert(k-1,ch[rt][(x>>k)&1]);
30 }
31 void solve(int k,int x){
32     if(sm[x]<=1)return;//printf("solve(%d,%d)\n",k,x);
33     if(k==-1){
34         ans=(long long)ans*qpow(sm[x],sm[x]-2)%p;
35         return;
36     }
37     solve(k-1,ch[x][0]);
38     solve(k-1,ch[x][1]);
39     if(sm[ch[x][0]]&&sm[ch[x][1]]){
40         mn=2147483647;
41         tmp=0;
42         dfs(k-1,ch[x][0],ch[x][1],1<<k);//printf("solve(%d,%d)\n",k,x);printf("mn=%d tmp=%d\n",mn,tmp);
43         sum+=mn;
44         ans=(long long)ans*tmp%p;
45     }
46 }
47 void dfs(int k,int x,int y,int now){
48     if(!sm[x]||!sm[y])return;
49     if(k==-1){
50         if(now<mn){
51             mn=now;
52             tmp=(long long)sm[x]*sm[y]%p;
53         }
54         else if(now==mn)tmp=(tmp+(long long)sm[x]*sm[y]%p)%p;
55         return;
56     }
57     if(sm[ch[x][0]]){
58         if(sm[ch[y][0]])dfs(k-1,ch[x][0],ch[y][0],now);
59         else dfs(k-1,ch[x][0],ch[y][1],now|(1<<k));
60     }
61     if(sm[ch[x][1]]){
62         if(sm[ch[y][1]])dfs(k-1,ch[x][1],ch[y][1],now);
63         else dfs(k-1,ch[x][1],ch[y][0],now|(1<<k));
64     }
65 }
66 int qpow(int a,int b){
67     int ans=1;
68     for(;b;b>>=1,a=(long long)a*a%p)if(b&1)ans=(long long)ans*a%p;
69     return ans;
70 }
View Code

 

51Nod1601 完全图的最小生成树计数