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;
}