ABC468E Sum of Average
问题
给定长度为 N 的整数序列 A。定义 f(l,r) 为子数组 Al,Al+1,…,Ar 的算术平均数,求
1≤l≤r≤N∑f(l,r)
对 998244353 取模后的结果。
数据规模:1≤N≤5×105,0≤Ai<998244353。
题目要求 1≤l≤r≤N∑(r−l+1Al+⋯+Ar),这是一个比较复杂的式子,显然我们可以写出一个 O(N2) 的暴力做法,然后枚举所有区间求和就行。Anyway,回头看一眼数据规模 …… 看来我们得优化一下。
常见的计数类优化思路是找贡献值,假设我们固定 Ai 不变,考虑每个 Ai 对答案的贡献,对于所有满足 l≤i≤r 的区间 [l,r],Ai 在该区间平均值中的贡献是:r−l+1Ai。
不妨记 ci=l≤i≤r∑(r−l+11),那么最终答案就是 i=1∑NciAi,接下来问题就转化为了如何计算这个系数 ci。
对于 i=1 来说,包含位置 1 的区间只能是 [1,1],[1,2],…,[1,N],所以 c1=1+21+31+⋯+N1。写起来好麻烦,记 fi=j=1∑ij1,那么 c1=fN。
接着考虑如何从 ci 转移到 ci+1。原本包含 i、但不包含 i+1 的区间,必须以 i 为右端点,即 [1,i],[2,i],…,[i,i];新增的包含 i+1、但不包含 i 的区间,必须以 i+1 为左端点,即 [i+1,i+1],[i+1,i+2],…,[i+1,N]。
前者的系数和是 1+21+⋯+i1=fi,后者的系数和是 1+21+⋯+N−i1=fN−i。于是我们就可以得到 ci+1=ci−fi+fN−i。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| void solve( ) { int n = read( ); ll p = 0; for( int i = 1; i <= n; ++ i ) a[i] = read( ); inv[1] = 1; for( int i = 2; i <= n; ++ i ) { inv[i] = ( mod - mod / i ) * inv[mod % i] % mod; }
f[0] = 0; for( int i = 1; i <= n; ++ i ) { f[i] = ( f[i - 1] + inv[i] ) % mod; }
c[1] = f[n]; for( int i = 1; i < n; ++ i ) { c[i + 1] = ( c[i] - f[i] + f[n - i] + mod ) % mod; }
ll ans = 0;
for( int i = 1; i <= n; ++ i ) { ans = ( ans + a[i] * c[i] ) % mod; }
cout << ans << '\n'; }
|
线性递推逆元公式
设 p=⌊ip⌋i+(pmodi)。
在模意义下整理,可以得到下列递推式:
inv[i]=(p−⌊ip⌋)⋅inv[pmodi](modp)。
写成代码就是:inv[i] = ( mod - mod / i ) * inv[mod % i] % mod; 可以当做结论记下来。
这是我赛事的推导思路,但在赛后整理思路的时候,我发现了另一种做法。
按照子数组长度分类,求出所有长度为 i 的子数组元素和之和,然后除以 i 分组计算贡献。
设 Ti=l=1∑n−i+1j=l∑l+i−1Aj 表示所有长度为 i 的子数组的元素和之和,那么长度为 i 的子数组平均数之和就是 iTi,最终答案就是 i=1∑niTi,问题就转化为了求出所有的 Ti。
关键是找 Ti 和 Ti+1 的关系,记前缀和为 Pi=j=1∑iaj。对于长度为 k 的窗口 [a1,…ak],[a2,…,ak+1],…,[an−k+1,…,an]。当窗口长度从 k 变成 k+1 时,可以把前 n−k 个长度为 k 的窗口向右扩展一个元素。
也就是 Tk+1=Tk−(an−k+1+⋯+an)+(ak+1+⋯+an),用前缀和表示就是 Tk+1=Tk−(Pn−Pn−k)+(Pn−Pk)=Tk+Pn−k−Pk。
于是在预处理 P 数组后就可以直接递推了,初始状态就很显然 T1=Pn 了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| void solve( ) { int n = read( );
for( int i = 1; i <= n; ++ i ) { a[i] = read( ); pre[i] = ( pre[i - 1] + a[i] ) % mod; }
inv[1] = 1; for( int i = 2; i <= n; ++ i ) { inv[i] = ( mod - mod / i ) * inv[mod % i] % mod; }
ll T = pre[n]; ll ans = 0;
for( int len = 1; len <= n; ++ len ) { ans = ( ans + T * inv[len] ) % mod;
if( len < n ) { T = ( T + pre[n - len] - pre[len] + mod ) % mod; } }
cout << ans << '\n'; }
|
记录
- 来源:ABC468 A~G;
- 归属:降维技巧;
- 新增:把区间平均值总和按单点贡献拆开,推导相邻位置系数递推;赛后又从固定长度窗口总和得到另一条等价递推。
- 分类:待定