CFGym103102I - Modulo Permutations(dp,计数)
题意
求长度为\(n\)且满足以下条件的排列有多少:
- \(p_i \mod p_{i+1} \le 2\),其中\(p_{n+1}=p_1\)。
题解
可以将排列分为两个段,一段以1为结尾,一段以2为结尾。这两段序列必须满足递减且满足条件。
那么相当于求,需要将\(n\)~\(3\)递减的数依次分别填入这两个段中合法的方案有多少。可以用当前两个段的结尾作为状态。设\((x,y)\)代表1段的结尾为\(x\),2段的结尾为\(y\)时的方案数。假设\(n\)~\(x\)都已经放完了,现在在放\(x-1\)(显然有\(i> x\)),那么有转移方程:
\[\begin{aligned}
(x,i) &\to (x-1,i) & x-1放在1段\\
&\to (x-1,x) & x-1放在2段,且(x-1 \mod i) \le 2
\end{aligned}
\]
直接可以压掉\(x\)那一维。第一个转移相当于将数组复制一遍,第二个转移只用转移\((x-1 \mod i) \le 2\)的\(i\)到\(x\)位置。用二维的模拟一下就知道了。最后时间复杂度为\(O(n\log n)\),调和级数。
细节:
使用\(n+1\)代表什么都不放时结尾的状态,转移的时候不要漏了\(n+1\)。
最后计算到\((1,2)\),答案为\(n\cdot (1,2)\)
#include <bits/stdc++.h>
#define endl '\n'
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define mp make_pair
#define seteps(N) fixed << setprecision(N)
typedef long long ll;
using namespace std;
/*-----------------------------------------------------------------*/
ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
#define INF 0x3f3f3f3f
const int N = 1e6 + 10;
const int M = 1e9 + 7;
const double eps = 1e-5;
ll dp[N];
int main() {
IOS;
int n;
cin >> n;
if(n <= 2) {
cout << n << endl;
return 0;
}
dp[n + 1] = 1;
for(int i = n; i >= 2; i--) {
ll tot = dp[n + 1];
if(i > 3) {
for(int j = (i - 1); j <= n; j += i - 1) {
if(j > i && j <= n) tot += dp[j];
if(j + 1 > i && j + 1 <= n) tot += dp[j + 1];
if(j + 2 <= n) tot += dp[j + 2];
tot %= M;
}
} else {
for(int j = i + 1; j <= n; j++) {
tot += dp[j];
tot %= M;
}
}
dp[i] = (dp[i] + tot) % M;
}
cout << dp[2] * n % M << endl;
}