1. 题目来源
相关题目:[M模拟] lc31. 下一个排列(模拟next_permutation函数+思维)
2. 题目解析
很困难的一个题目,首先得破解题意,其次还得知道康拓展开或者数位 dp
的相关知识,最后还得知道快速幂、费马定理求逆元及重排问题的方案数求解…在此我只能写写思路…没看的太懂。
思路:
- 该问题的操作可以抽象为求该排列的上一个排列,然后最终将其恢复成一个字典序排列,也就是求原题排列在全排列中的序号是多少。
- 有固定方法,康拓展开,专门来求解排列序号问题。也可以直接数位
dp
。 - 关于重复元素需要考虑重排问题,即若
n
个长度的字符串中,有n1
个a
,n2
个b
,n3
个c
。那么就有 n ! n 1 ! ⋅ n 2 ! ⋅ n 3 ! \frac {n!} {n_1! \cdot n_2! \cdot n_3!} n1!⋅n2!⋅n3!n! 种不同的排列方法。针对上式就需要预处理阶乘、由于有除法的存在,需要考虑 n ! n! n! 的逆元,且由于MOD=1e9+7
,是个质数,那么久可以采用裴蜀定理配合费马小定理,快速求得逆元。
官方题解写的很好,可以去学习学习,公式推导就很明确。
- 时间复杂度: O ( 不 会 分 析 ) O(不会分析) O(不会分析)
- 空间复杂度: O ( 不 会 分 析 ) O(不会分析) O(不会分析)
代码:
typedef long long LL;
const int MOD = 1e9 + 7;
const int N = 3010;
class Solution {
public:
LL f[N], g[N];
LL qmi(LL a, int b) {
LL res = 1;
while (b) {
if (b & 1) res = res * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return res;
}
LL get(int cnt[]) {
LL res = 1, sum = 0;
for (int i = 0; i < 26; i ++ ) sum += cnt[i];
res = f[sum];
for (int i = 0; i < 26; i ++ )
res = res * g[cnt[i]] % MOD;
return res;
}
int makeStringSorted(string s) {
f[0] = g[0] = 1;
for (int i = 1; i <= s.size(); i ++ ) {
f[i] = f[i - 1] * i % MOD;
g[i] = qmi(f[i], MOD - 2);
}
LL res = 0;
int cnt[26] = {0};
for (auto c: s) cnt[c - 'a'] ++ ;
for (int i = 0; i < s.size(); i ++ ) {
int x = s[i] - 'a';
for (int j = 0; j < x; j ++ ) {
if (!cnt[j]) continue;
cnt[j] -- ;
res = (res + get(cnt)) % MOD;
cnt[j] ++ ;
}
cnt[x] -- ;
}
return res;
}
};