CF1542E2 - Abnormal Permutation Pairs (hard version)(dp,优化技巧)

题目

两个长度为n的排列p,q,问满足以下条件的排列有多少

  • p的字典序小于q
  • p中的逆序数对大于q中的逆序数对

输出答案模mod,\(n\le 500\)

题解

在easy version中,\(n\le 50\),想想怎么做。

由于p字典序小于q,可以枚举第一个p小于q的位置。然后之后的排列就可以随意了。因为在该位置p小于q,所以有一个初始的逆序数差值。然后问题转化为求长度为n的排列p,q之间逆序对数相差为k的(p,q)对有多少对。完成这个之后的统计答案就简单了。

\(dp[i][j]\)代表长度为\(i\),逆序对数差为\(j\)的排列对数。这里\(|j|\le\frac{i(i-1)}{2}\)。显然\(j\)可以取负数,但是由于\(dp[i][j]=dp[i][-j]\)所以可以只处理正数部分。

转移很简单:

\[dp[i][j]=\sum_{|k|<i}{(i-|k|)\cdot dp[i-1][|j-k|]} \\ dp[0][0] = 1 \]

时间复杂度\(O(n^4)\)\(n\le 50\)可以过。

对于\(n \le 500\),得想办法优化到\(O(n^3)\)。观察\(dp[i][j-1]\)\(dp[i][j]\),会发现很有规律,它们的差值刚好就是两个区间和。所以暴力算出\(dp[i][0]\),后面之间用前缀和算即可。

#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 = 500 + 10;
const int M = 3e5 + 10;
const double eps = 1e-5;

ll dp[M], sum[M];
ll tmpdp[M], tmpsum[M];
ll C[N][N];
ll fact[N];
int sq, n, mod;

ll getsum(int p, int l, int r) {
    if(l > r) return 0;
    ll res = 0;
    if(r < 0) {
        r = -r;
        l = -l;
        swap(l, r);
    }
    if(l <= 0) {
        res = sum[min(-l, sq)];
        l = 1;
    }
    res = (res + sum[min(r, sq)] - sum[l - 1] + mod) % mod;
    return res;
}

int main() {
    IOS;
    cin >> n >> mod;
    fact[0] = 1;
    for(int i = 1; i < N; i++) {
        C[i][0] = C[i][i] = 1;
        fact[i] = fact[i - 1] * i % mod;
    }
    for(int i = 1; i < N; i++) {
        for(int j = 1; j < i; j++) {
            C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod;
        }
    }
    ll ans = 0;
    sq = n * (n - 1) / 2;
    dp[0] = sum[0] = 1;
    for(int i = 1; i <= sq; i++) sum[i] = sum[i - 1];
    for(int i = 1; i <= n; i++) {
        tmpdp[0] = 0;
        for(int k = 1 - i; k <= i - 1; k++) {
            tmpdp[0] = (tmpdp[0] + (i - abs(k)) * dp[abs(k)]) % mod; 
        }
        tmpsum[0] = tmpdp[0];
        for(int j = 1; j <= sq; j++) {
            tmpdp[j] = tmpdp[j - 1] - getsum(i - 1, j - i, j - 1) + mod + getsum(i - 1, j, j + i - 1);
            tmpdp[j] %= mod;
            tmpsum[j] = (tmpsum[j - 1] + tmpdp[j]) % mod;
        }
        for(int j = 0; j <= sq; j++) {
            dp[j] = tmpdp[j];
            sum[j] = tmpsum[j];
        }
        for(int j = sq - 1; j >= 0; j--) {
            tmpdp[j] = (tmpdp[j + 1] + tmpdp[j]) % mod;
        }
        for(int j = 1; j <= i; j++) {
            ans = (ans + fact[n - i - 1] * abs(i + 1 - j) % mod * C[n][n - i - 1] % mod * tmpdp[j + 1] % mod) % mod;
        }
    }
    cout << ans << endl;
}
posted @ 2021-09-20 00:31  limil  阅读(56)  评论(0编辑  收藏  举报