• 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;
    }
    
  • 相关阅读:
    升级系统引起的
    php curl批处理
    看《黑炮事件》想到的
    查找单链表的倒数第k个值
    python绘制树枝
    暑假第九测
    网络流24题(持续更新)
    P2657 [SCOI2009]windy数
    P3177 [HAOI2015]树上染色
    暑假第六测
  • 原文地址:https://www.cnblogs.com/limil/p/15380932.html
Copyright © 2020-2023  润新知