题目链接:https://codeforces.com/problemset/problem/1091/D
题目大意:
给定数字n,把n的全排列按字典序放入p数组中,这个数组的长度即n*n!.问你这个p数组中有多少这样的段,一段里的数字之和为n(n+1)/2.结果取模1e9 + 7.
解题思路:
明显地,1+2+…+n = n*(n+1)/2.盲目地找肯定是不行的,必须要有点规律下手。题目有说明“按字典序排列”,也就是说在一段区间内必然有一些前缀相同的全排列子阵。不妨试求前缀相同的子阵所含的答案数。设相同前缀长度为i,此时前缀的选法为C(n, i) * i!,为了保证相同前缀长度严格等于i,我们要使相邻两个排列第i+1位数不同,还需要乘(n - i - 1), 最后,由于区间可滑动,答案再乘 i。只需枚举相同前缀长度即可得出最终答案。
代码如下:
# include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 5;
const int mod = 998244353;
typedef long long ll;
ll fac[maxn], inv[maxn];
int n;
ll qpow(ll a, ll b){
ll res = 1;
while(b){
if(b&1) res = res*a % mod;
a = a*a % mod;
b >>= 1;
}
return res;
}
void init(){
fac[0] = fac[1] = 1;
for(int i = 2; i < maxn; ++i)
fac[i] = fac[i-1] * i % mod;
inv[maxn-1] = qpow(fac[maxn-1], mod-2);
for(int i = maxn-1; i >= 0; --i)
inv[i-1] = inv[i] * i % mod;
}
ll C(int n, int m){
return fac[n] * inv[n-m] %mod * inv[m] %mod;
}
int main(){
std::ios::sync_with_stdio(false);
init();
//cout << inv[2] << endl;
while(cin >> n){
ll res = fac[n];
for(int i = 1; i < n-1; ++i)
res = (res + (i*C(n, i) % mod *(n-i-1) % mod * fac[i] % mod)) % mod;
//*i是滑动所得,C(n, i)是长度为i前缀组合,*(n-i-1)保证严格长度为i,*fac[i]是对前缀全排列
cout << res << endl;
}
return 0;
}