您的位置:首页 > 其它

[JZOJ5352]【NOIP2017提高A组模拟9.7】计数题

2017-09-08 22:53 549 查看

Description

给定N个点,每个点有权值a[i]。

定义一条无向边x,y,权值为a[x] xor a[y]

求这N个点构成的完全图的最小生成树

的边权和

以及它的方案数,方案数对1e9取模

N<=105,0≤a[i]≤230

Solution

既然是异或,我们可以按位考虑。

用分治的思想

从高位到低位扫,对于当前位可以将处理的点分成两部分,分别是这一位上为0和为1的点

最优解一定是这两部分分别自己做最小生成树,然后这两部分之间连一条边。

因为其他的这一位上都是0,只有这条边这一位是1,根据2进制的性质这样一定最优

两部分可以递归处理,这两部分之间连边可以维护一个字典树处理,方案数直接乘进去。

特殊情况是当前处理的部分的点已经分治到2^0都不能分开,也就是当前部分所有点权值相同。

对边权和贡献一定是0,方案数就是完全图中生成树的数量

有公式

对于N个点完全图,生成树的数量为NN−2,当N>1

证明我还不会,留坑代填。。。

分析复杂度

分治每个二进制位只有一层,总共有log 层,每一层把所有点扫一遍,用字典树维护复杂度N log Maxa

总的复杂度是O(Nlog2Maxa)

Code

#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cmath>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fod(i,a,b) for(int i=a;i>=b;i--)
#define N 100005
#define LL long long
#define mo 1000000007
using namespace std;
int n,a
,n1,vl;
LL vs,ans;
struct node
{
int a[2];
LL s;
}tr[30*N];
LL ksm(LL k,LL n)
{
if(n<1) return 1;
LL s=ksm(k,n/2);
return (n%2)?s*s%mo*k%mo:s*s%mo;
}
void dfs(LL v,int l,int r)
{
if(l>=r) return;
if(v==0)
{
(vs*=ksm(r-l+1,r-l-1))%=mo;
return;
}
int mid=r+1;
LL mi=2147483647,ms=0;
fo(i,l,r)
if(a[i]&v)
{
mid=i;
break;
}
dfs(v/2,l,mid-1),dfs(v/2,mid,r);
if(mid==l||mid==r+1) return;
n1=1,tr[1].a[0]=tr[1].a[1]=tr[1].s=0;
fo(i,l,mid-1)
{
LL vt=vl,k=1;
while(vt)
{
LL p=((vt&a[i])>0);
if(!tr[k].a[p]) tr[k].a[p]=++n1,tr[n1].a[0]=tr[n1].a[1]=tr[n1].s=0;
k=tr[k].a[p],vt>>=1,tr[k].s++;
}
}
fo(i,mid,r)
{
LL vt=vl,k=1,sq=0;
while(vt)
{
LL p=((vt&a[i])>0);
if(!tr[k].a[p]) sq+=vt,k=tr[k].a[1-p];
else k=tr[k].a[p];
vt>>=1;
}
if(sq==mi) (ms+=tr[k].s)%=mo;
if(sq<mi) ms=tr[k].s,mi=sq;
}
ans+=mi,(vs*=ms)%=mo;
}
int main()
{
freopen("jst.in","r",stdin);
freopen("jst.out","w",stdout);
cin>>n;
vl=0;
fo(i,1,n) scanf("%d",&a[i]),vl=max(vl,a[i]);
vl=1<<(int)log2(vl);
ans=0,vs=1;
sort(a+1,a+n+1);
dfs(vl,1,n);
printf("%lld\n%lld",ans,vs);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: