BZOJ NOI 十连测 哈夫曼树
【问题描述】
有这样一个经典问题:
∙ 给出一个长度为𝑛的非负整数数组𝑎。
∙ 每次可以选择数组中两个不同位置的数𝑎𝑖, 𝑎𝑗(𝑖 ̸= 𝑗),将它们删除,然
后再向数组中加入一个新的元素,值为𝑎𝑖 + 𝑎𝑗。
∙ 这样一次操作产生的代价是这个新元素的值,即𝑎𝑖 + 𝑎𝑗。
∙ 例如当前数组中的数为𝑎 = {1, 1, 3, 1},选择𝑎1 = 1, 𝑎4 = 1进行操作
后,数组变为{1, 3, 2},代价为2。
∙ 一共会进行𝑛 − 1次操作,要求最小化代价之和。
这道题可以用经典的哈夫曼树算法解决,然而小𝐷正在𝑁𝑂𝐼考场上,
根本不会哈夫曼树,他决定凭信仰出奇迹。
小𝐷每次选数都是随机选两个数,随机规则为:
∙ 假如有三个数𝑥, 𝑦, 𝑧,可能有相等的数,但是不影响选取。
∙ 小𝐷会等概率选取(𝑥, 𝑦)(𝑥, 𝑧)(𝑦, 𝑧)中的一对。
现在小𝐷想知道他的程序的期望输出。
设期望输出为𝑎𝑛𝑠,为了避免精度误差,你只需要输出下述式子在
模109 + 7域下的值。
𝑎𝑛𝑠 ×
𝑛 Π︁𝑖=2
𝑖(𝑖 − 1)
【数据规模】
对于20%的数据,𝑛 ≤ 5。
对于另10%的数据,数组𝑎中最多只有5个正整数。
对于另10%的数据,数组𝑎中的数全部相同。
对于另30%的数据,𝑛 ≤ 103。
SOL:我们发现 ans=sigma ai* 一个系数。
我们又发现答案要求的是所有方案的和。
我们不难发现这个系数是关于n的函数。
我们构造一个有n-2个1,一个2的序列。
我们知道n个1通过一步后必然会成上一次的结果。
那么我们可以对n个1的情况构造等式:
f[n]*n(n个1的答案)=(f[n-1]*n(n-2个1,一个2的答案)+2(合并1次的代价)*S当前方案数)*2Cn(在n个中选2个方案数)
我们不难发现S当前方案数=前n-2个三角型数的积。
所以·O(N)跑一趟就好了。
#include<bits/stdc++.h> #define mo 1000000007 #define iv2 500000004 using namespace std; int n,p; long long f[1000007],s,sum; signed main () { freopen("huffman.in","r",stdin); freopen("huffman.out","w",stdout); scanf("%d",&n); f[1]=0; f[2]=1; s=1; for (int i=3;i<=n;i++) { f[i]=(f[i-1]*i%mo+2*s)%mo*(i-1)%mo*iv2%mo; s=s*i%mo*(i-1)%mo*iv2%mo; } // cout<<f[n]; for (int i=1;i<=n;i++) scanf("%d",&p),sum+=p; sum%=mo; printf("%lld\n",sum*f[n]%mo); return 0; // cout<<(7830*7+2*1*3*6*10*15)*6/2; }