1 条题解
-
0
E. Max History 题解
一、题目回顾
给定长度为 的数组 ,定义 :
- 初始 ,
- 对 ,若 ,则 ,然后
求 在所有 种排列下的总和,对 取模。
二、组合分析
2.1 过程的意义
扫描排列时, 始终指向当前遇到的最大值。每当遇到一个严格更大的元素时,旧的最大值被累加到 中,然后更新 。
因此 等于:排列中所有前缀最大值(prefix maximum)的前驱之和。换言之,除了全局最大值外,每个曾经成为前缀最大值的元素都会贡献一次自身的值。
2.2 一个元素何时贡献
考虑值 的某个具体副本。它在排列中贡献 当且仅当:
- 所有严格大于 的元素都出现在它之后(否则它无法成为前缀最大值);
- 所有等于 的其他副本也都出现在它之后(若前面已有一个等于 的前缀最大值,则 ,条件 不成立)。
令:
- = 值 的元素个数(即严格大于 的个数 等于 的个数);
- = 值等于 的元素个数。
对于一个确定的 副本,它需要排在其余 个元素(所有 的其他元素)之前。
在所有 种排列中,该副本恰好在这些 个元素中排第一的排列数为:
2.3 总贡献
每个等于 的副本贡献相同。共有 个副本,因此所有值为 的元素对总和的贡献为:
对所有非全局最大值的值 求和即可。
三、算法步骤
- 对数组 排序。
- 预处理阶乘 及逆元。
- 遍历排序后的数组,将相同值分为一组:
- 设当前组值为 ,出现次数 ;
- 设值 的元素个数 (其中 为该组最后一个元素的下标, 为严格大于 的元素个数);
- 若 不是全局最大值(即 或还有更大元素),则贡献:
- 输出总和模 。
四、复杂度
- 排序
- 预处理阶乘与逆元
- 遍历分组
总复杂度 ,可通过 。
五、AC 代码
#include <bits/stdc++.h> using namespace std; const int N = 1000005; const int mod = 1000000007; int n; int a[N]; int ft[N], ift[N]; // ft: 阶乘, ift: 阶乘逆元 typedef long long ll; ll qp(ll x, int y) { ll ans = 1; while (y) { if (y & 1) ans = ans * x % mod; x = x * x % mod; y >>= 1; } return ans; } int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", &a[i]); sort(a + 1, a + n + 1); // 预处理阶乘 ft[0] = 1; for (int i = 1; i <= n; i++) ft[i] = 1LL * ft[i - 1] * i % mod; // 预处理阶乘逆元 ift[n] = qp(ft[n], mod - 2); for (int i = n; i >= 1; i--) ift[i - 1] = 1LL * ift[i] * i % mod; int cnt = 0, ans = 0; for (int i = 1; i <= n; i++) { ++cnt; if (a[i] < a[i + 1] && i < n) { // v = a[i], cnt = 当前组大小 // S = n - i + cnt = 值 >= v 的元素个数 // 贡献: v * cnt * n! / S int S = n - i + cnt; ans = (ans + 1LL * ft[n] * ft[S - 1] % mod * ift[S] % mod * a[i] % mod * cnt) % mod; cnt = 0; } } printf("%d\n", ans); return 0; }代码细节说明
ft[n] * ft[S-1] % mod * ift[S]等价于 ,利用了 以及 。- 全局最大值(排序后最后一个元素)不会被处理,因为没有 触发它的累计,其贡献自然为 。
- 1
信息
- ID
- 6762
- 时间
- 3000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者