86 lines
2.6 KiB
Markdown
86 lines
2.6 KiB
Markdown
要解决这个问题,我们需要计算一个随机排列需要多少次冒泡排序才能完全有序的期望值。对于这个问题,我们可以采用概率和数学期望的知识进行计算。
|
||
|
||
### 思路分析
|
||
|
||
1. **冒泡排序的特性**:
|
||
- 在每一趟冒泡排序中,至少有一个元素会被移到最终位置。
|
||
- 因此,一个长度为 `n` 的排列最多需要 `n-1` 趟冒泡排序。
|
||
|
||
2. **逆序对**:
|
||
- 冒泡排序的趟数与排列的逆序对数量密切相关。
|
||
- 一个逆序对 `(i, j)` 满足 `i < j` 且 `a[i] > a[j]`。
|
||
- 每次冒泡排序至少消除一个逆序对。
|
||
|
||
3. **期望值计算**:
|
||
- 我们需要计算的是在随机排列下冒泡排序趟数的期望值。
|
||
- 设 `E(n)` 为长度为 `n` 的随机排列需要的冒泡排序的期望趟数。
|
||
|
||
### 数学推导
|
||
|
||
对于一个长度为 `n` 的随机排列,它的逆序对数量期望为 `n*(n-1)/4`。因为每次冒泡排序消除一个逆序对,逆序对数量减为 0 所需的期望趟数可以看作是期望逆序对数。
|
||
|
||
### 动态规划优化
|
||
|
||
1. **定义状态**:
|
||
- 我们用 `dp[i]` 表示长度为 `i` 的排列需要的期望冒泡排序趟数。
|
||
|
||
2. **状态转移**:
|
||
- 对于长度为 `i` 的排列,期望趟数 `dp[i]` 可以由长度为 `i-1` 的排列推导出来,加上在长度为 `i` 的排列上添加一个新的元素后的期望趟数变化。
|
||
|
||
3. **最终结果**:
|
||
- 我们需要将结果对 `1e9+7` 取模。
|
||
|
||
### C++ 实现
|
||
|
||
以下是求解该问题的 C++ 代码:
|
||
|
||
```cpp
|
||
#include <iostream>
|
||
#include <vector>
|
||
using namespace std;
|
||
|
||
const int MOD = 1e9 + 7;
|
||
|
||
int mod_inv(int a, int m) {
|
||
int m0 = m, t, q;
|
||
int x0 = 0, x1 = 1;
|
||
if (m == 1) return 0;
|
||
while (a > 1) {
|
||
q = a / m;
|
||
t = m;
|
||
m = a % m, a = t;
|
||
t = x0;
|
||
x0 = x1 - q * x0;
|
||
x1 = t;
|
||
}
|
||
if (x1 < 0) x1 += m0;
|
||
return x1;
|
||
}
|
||
|
||
int main() {
|
||
int n;
|
||
cin >> n;
|
||
|
||
vector<long long> dp(n + 1, 0);
|
||
for (int i = 2; i <= n; ++i) {
|
||
dp[i] = (dp[i - 1] + (i * (i - 1) / 2) % MOD * mod_inv(i, MOD)) % MOD;
|
||
}
|
||
|
||
cout << dp[n] << endl;
|
||
return 0;
|
||
}
|
||
```
|
||
|
||
### 解释
|
||
|
||
1. **求模逆元**:
|
||
- 使用扩展欧几里得算法求模逆元。
|
||
|
||
2. **动态规划**:
|
||
- `dp[i]` 表示长度为 `i` 的排列需要的期望冒泡排序趟数。
|
||
- 通过前一状态 `dp[i-1]` 递推得到当前状态 `dp[i]`。
|
||
|
||
3. **输出结果**:
|
||
- 最终结果输出 `dp[n]`,即长度为 `n` 的排列所需的期望冒泡排序趟数。
|
||
|
||
该代码有效地计算了长度为 `n` 的随机排列的期望冒泡排序趟数,并对结果取模 `1e9+7`。 |