题目
长度为(n)的排列中,对于一个数(x),如果所有包含(x)的区间的最大值恰有(m)个不同的值,那么(x)是好的。问有多少长度为(n)的排列,给定(m),恰好有(k)个好的的数?(nle 100)
题解
感觉不好直接计数,试试动态规划。设(f[n][m][k])代表满足条件的排列数。可以发现一个很好的性质,每加入一个新的数(n+1),它会分别使得其左右部分中数的不同最大值个数加1。枚举插入(n+1)的位置,容易得到转移方程:
[f[n][m][k]=sumlimits_{t=0}^{n-1}{C(n-1,t)sum_{a=0}^{k}{f[t][m-1][a] imes f[n-1-t][m-1][k-a]}}
]
这样多状态的转移方程最麻烦的就是边界条件的设置!要严格按照(f)的含义设置边界条件。容易发现,最外层枚举(m)比较好,因为转移都是从(m-1)到(m)。那么要设置的边界值就是(f[n][1][k])。
如何设置边界值?对于(f[n][1][k]),会发现比较难确定(k=0)时的值。这是因为不知道(f[n][m][0])的值是多少。这时,假如把(f)的定义改一下,改成有大于等于(k)个好的的数,令其为(g)那么显然有
[g[n][m][k]=sum_{i=k}^{infty}{f[n][m][i]} \
g[n][m][0]=n!
]
因为大于等于0个就是所有排列的情况。那么(k)恰好等于0的情况就是就是(f[n][m][0] = g[n][m][0]-g[n][m][1])。那么有(g[n][1][0]=n!(nge 0)),(g[n][1][1]=n!(n>0)),其余情况都是0。那么就有边界情况:
[egin{aligned}
f[n][1][1]&=n!\
f[0][1][0]&=1
end{aligned}
]
其余情况都为0,这样就有边界值(f[n][1][k])啦。直接套(dp)方程,轻松秒杀(doge)。
时间复杂度(O(n^2mk^2))?是不是有点太慢了?确实慢,因此需要剪枝。因为有(mle n),(k le n-m+1)的限制,可以减少一点计算。即使如此,2s能跑这个复杂度,还是觉得有点神奇,然而事实就是这样(官方题解也差不多是这个复杂度),小编也感到很疑惑。
细节:
- 注意在使用限制减少计算量时,(n)和(k)取到0时是例外,不能被省略了。比如要记得预处理(f[n][m][0])的值。
- 0是十分特殊的位置,要注意。它的结果是(所有情况-非0情况)。不要漏了0处的值,也不要想当然觉得它的值就是0。
- 取模真的很耗时,在里面的循环使用
long long
,直接累加不取模,然后到外面循环再取模。速度提升效果显著。
#include <bits/stdc++.h>
#define endl '
'
#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 = 110;
const double eps = 1e-5;
int C[N][N];
int dp[N][N][N];
int fact[N];
inline int cal(int m, int n, int k, int p) {
ll res = 0;
for(int t = 0; t <= n - 1; t++) {
ll tres = 1ll * dp[m - 1][t][0] * dp[m - 1][n - 1 - t][k] % p;
if(k) tres <<= 1;
if(t >= m - 1 && t <= n - m) {
for(int a = max(1, k - n + t + m - 1); a <= min(k - 1, t - m + 2); a++) {
tres = tres + 1ll * dp[m - 1][t][a] * dp[m - 1][n - 1 - t][k - a] % p;
}
}
tres %= p;
res = (res + C[n - 1][t] * tres % p);
}
return res % p;
}
int main() {
IOS;
int n, m, k, p;
cin >> n >> m >> k >> p;
fact[0] = 1;
for(int i = 1; i <= n; i++) {
fact[i] = 1ll * fact[i - 1] * i % p;
C[i][0] = C[i][i] = 1;
for(int j = 1; j < i; j++) {
C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % p;
}
}
for(int i = 0; i <= n; i++) {
for(int j = 0; j <= n; j++) {
dp[j][i][0] = fact[i];
}
}
for(int i = 1; i <= n; i++) {
dp[1][i][1] = fact[i];
dp[1][i][0] = 0;
}
for(int i = 2; i <= m; i++) {
for(int j = i; j <= n; j++) {
ll sum = 0;
for(int k = 1; k <= j - i + 1; k++) {
dp[i][j][k] = cal(i, j, k, p);
sum += dp[i][j][k];
}
dp[i][j][0] -= sum % p;
dp[i][j][0] = (dp[i][j][0] + p) % p;
}
}
cout << dp[m][n][k] << endl;
}