题目链接:http://www.51nod.com/Challenge/Problem.html#problemId=1601
题目大意:
思路:
这是一张完全图,并且边的权值是由点的权值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<bits/stdc++.h>
#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;
}