题目大意。
在一条直线有n个节点,每个节点有一个权值ai。
一段路从某个起点1开始,到右端某个节点b结束(1 ≤ i ≤ j ≤ n),这段路程的价值w为经过点点权的平均值,即:
w = ∑ i = a b a i b − a + 1 w=\frac{\sum^{b}_{i=a}a_i } {b-a + 1} w=b−a+1∑i=abai
问题为对于这n个节点的路,任意选择两个顶点,其路程价值的期望值为多少?结果对1e9 + 7取模
首先应该分析出来,对于取模下的期望值和平均值,不需要真的计算除法,而是需要计算各分母的逆元,使用乘法代替除法。
对于总方案数tot
,由于是随意选取两个顶点作为起点和终点,且起点和终点可以重合,那么总方案数应该为:
t o t = C n 2 + n = n ( n + 1 ) 2 tot=C_{n}^{2}+n=\frac{n(n+1)} {2} tot=Cn2+n=2n(n+1)
要算贡献总期望值,那么公式就是
贡 献 总 和 总 方 案 数 \frac{贡献总和} {总方案数} 总方案数贡献总和
刚刚我们得出了总方案数,下面就需要来计算贡献总和。
对于节点i
来说,其贡献应该为 ai*每次出现所占所有元素的比例
,
我们不妨定义:
第 i 个 元 素 在 j 区 间 长 度 内 出 现 次 数 = s u m 第i个元素在j区间长度内出现次数=sum 第i个元素在j区间长度内出现次数=sum
即
节 点 i 贡 献 = a i ∑ j = 1 n s u m j 节点i贡献=a_i\sum_{j=1}^{n} \frac{sum} {j} 节点i贡献=aij=1∑njsum
因此,贡献总和的公式为:
贡 献 总 和 = ∑ i = 1 n a i ∑ j = 1 n s u m j 贡献总和=\sum_{i=1}^{n}a_i\sum_{j=1}^{n} \frac{sum} {j} 贡献总和=i=1∑naij=1∑njsum
到这一步,我们发现,j会遍历从1到n的每一个数字,因此我们需要预处理从1到n内所有整数的逆元
void init(int n){
inv[1] = 1;
sumInv[1] = 1;
for(int i = 2;i <= n;i++){
inv[i] = (p - p / i) * inv[p % i] % p;
(sumInv[i] = sumInv[i - 1] + inv[i]) %= p;
}
}
这里由于存在多组数据,n大小不确定,因此直接取n最大值2e5即可
接着我们要分析一个比较重要的地方,就是当区间长度为j时,在所有的区间内,第i个元素究竟会出现多少次。
我们不妨对j从小到大分析,可以大致分为几个阶段,这里题解写的非常详细,就偷个懒 直接放原图了:
按照这样直接处理,会有些麻烦,当下标超过了n/2时,整个规则将会反过来。
仔细观察会发现,由于对称性,第i个元素和第n-i+1个元素的出现次数和出现期望,是完全一样的,因此不如充分发挥数据的对称性,仅处理一般的数字。
贡 献 总 和 = ∑ i = 1 ⌊ n 2 ⌋ ( a i + a n + 1 − i ) ∑ j = 1 ⌊ n 2 ⌋ s u m j 贡献总和=\sum_{i=1}^{\left \lfloor \frac{n} {2}\right \rfloor}(a_i+a_{n+1-i})\sum_{j=1}^{\left \lfloor \frac{n} {2}\right \rfloor}\frac{sum} {j} 贡献总和=i=1∑⌊2n⌋(ai+an+1−i)j=1∑⌊2n⌋jsum
这样一来,sum的取值就分成三段了:
sum=j
sum=i
sum=n-j+1
然后对于每个i,直接分成三段来求就好了,求每个i的贡献值复杂度为O(1),第三段可以迭代来求,不需要每次遍历。于是整个求解过程的复杂度就是O(1)了。
什么?你问我奇数怎么办?好搞,n为奇数时中间那个点单独处理一下就好了,它的每个sum=min(j,n-j+1)
(j为区间长度)
在统计完贡献总和后,我们离答案就差临门一脚了,就是除以方案总数,最终的结果式为:
a n s = ∑ i = 1 n a i ∑ j = 1 n s u m j n ( n + + 1 ) 2 ans=\frac{\sum_{i=1}^{n}a_i\sum_{j=1}^{n}\frac{sum} {j}} {\frac{n(n++1)} {2}} ans=2n(n++1)∑i=1nai∑j=1njsum
方案总数的逆元,可以使用快速幂的方法来求。
fpow(1LL * (n + 1) * n / 2,p - 2) ;
#include
#include
using namespace std;
typedef long long ll;
const ll N = 1e7 + 50;
const ll p = 1e9 + 7;
ll inv[N];
ll sumInv[N];
ll a[N];
int n,T;
ll ans = 0;
ll c = 0;
void init(int n){
inv[1] = 1;
sumInv[1] = 1;
for(int i = 2;i <= n;i++){
inv[i] = (p - p / i) * inv[p % i] % p;
(sumInv[i] = sumInv[i - 1] + inv[i]) %= p;
}
}
inline ll min(const ll &a,const ll &b){
return a < b ? a : b;
}
ll fpow(ll k,ll po){
k %= p;
ll ans = 1;
for(;po;po >>= 1,k = k * k % p){
ans = po & 1 ? ans * k % p : ans;
}
return ans;
}
int main(){
freopen("1.in","r",stdin);
init(200050);
for(cin >> T;T;T--){
scanf("%d",&n);
for(int i = 1;i <= n;i++){
scanf("%lld",&a[i]);
}
c = 0;
ans = 0;
for(int i = 1;i <= n >> 1;i++){
/*j <= i时,有i种情况,每种情况的贡献值都是j/j=1,总贡献为i*/
ll sum = i;
/* i < j < n - i时,每种情况的贡献值为i/j,总贡献就是i*Σ(j^-1)*/
(sum += i * (sumInv[n - i] - sumInv[i] + p) % p) %= p;
/*j > n - i时,每种情况的贡献值为(n - j + 1)/j为方便计算可以预处理或者累计
当j = n - i + 1时,这个值为i/j*/
(c += i * inv[n - i + 1] % p) %= p;
(sum += c) %= p;
(ans += sum * a[i] % p) %= p;
(ans += sum * a[n - i + 1] % p) %= p;
}
/*奇数个数单独处理*/
if(n & 1){
ll sum = 0;
for(int i = 1;i <= n;i++){
/*对于区间长度i,出现次数为min(i,n - i + 1)贡献为min(i,n - i + 1)/i*/
(sum += min(i,n - i + 1) * inv[i] % p) %= p;
}
(ans += sum * a[(n>>1)+1] % p) %= p;
}
cout << ans * fpow(1LL * (n + 1) * n / 2,p - 2) % p << endl;
}
}