CF1581D

题目

长度为(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;
}
原文地址:https://www.cnblogs.com/limil/p/15380932.html