2020杭电多校第六场 1006 Road To The 3rd Building

题目

2020杭电多校第六场 1006 Road To The 3rd Building_第1张图片

题目大意。
在一条直线有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=ba+1i=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 ij=sum

节 点 i 贡 献 = a i ∑ j = 1 n s u m j 节点i贡献=a_i\sum_{j=1}^{n} \frac{sum} {j} i=aij=1njsum
因此,贡献总和的公式为:
贡 献 总 和 = ∑ 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=1naij=1njsum
到这一步,我们发现,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区间长度内出现次数

接着我们要分析一个比较重要的地方,就是当区间长度为j时,在所有的区间内,第i个元素究竟会出现多少次。

我们不妨对j从小到大分析,可以大致分为几个阶段,这里题解写的非常详细,就偷个懒 直接放原图了:

2020杭电多校第六场 1006 Road To The 3rd Building_第2张图片
按照这样直接处理,会有些麻烦,当下标超过了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=12n(ai+an+1i)j=12njsum
这样一来,sum的取值就分成三段了:

  1. 1 ≤ j ≤ i,sum=j
  2. i < j < n-i,sum=i
  3. n-i+1 ≤ j ≤ n,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=1naij=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;
	}
}

你可能感兴趣的:(#,杭电多校,算法,数据结构,数论,乘法逆元)