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;
}
posted @ 2021-10-06 12:29  limil  阅读(281)  评论(0编辑  收藏  举报