zoukankan      html  css  js  c++  java
  • The Journey of Geor Autumn 上海ICPC 2020 E dp新姿势

    The Journey of Geor Autumn 上海ICPC 2020 E dp新姿势

    题目大意:

    求大小是 (n) 且满足条件的排列有多少,条件是:给定k,对于任意 (i>k \,\,&\,\,i<=n)(i) ,存在 (a_i>min(a_{i-k},...,a_{i-1}))

    题解:

    首先先去试着构造这个排列,首先明确1一定在前面k个数中,任意选择一个位置放1,那么2可以在1的左边,此时可以理解为2前面比她小的是0,也可以放在距离1不超过 (k) 的右边,假设1和2相差 (j) ,那么1和2中间可以填 (j-1) 个数,这些数是没有任何限制的,如果3已经被填了,那么继续往后考虑其他的数,以这样的构造思维来定义这个 (dp)

    先写出复杂度是 (O(nk)) 的算法,定义 (dp[i]) 表示

    • 表示已经放了前 i 个位置
    • i 这个位置的值小于当前还没有放的所有值
    • 往前推 j 个, i 这个位置的值也是最小的

    这样最后答案就是 (dp[n])

    给出 (dp) 定义之后可以自己写转移方程了

    (dp[i] += dp[i-j]*A(n-i+j-1,j-1)%mod;)

    (dp[i])(dp[i-j]) 转移过来,说明 (dp[i-j]<dp[i])

    (A(n-i+j-1,j-1)) 因为从 (i-j)(i) 一共有 (j) 个数,中间可以填(j-1)个数,底数是 (n-i+j-1) 表示的是从 (i-j)(n) 还有 (n-i+j) 个数,但是因为 (i) 这个位置的值既要小于当前还没有放的所有值,又要是往前推 (j) 个的最小值,所以 (i) 这个位置的值一定是最小值,所以要去掉这个值,所以只有 (n-i+j-1) 这么多个数的选择。

    dp[0] = 1;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=min(i,k);j++){
            dp[i] += dp[i-j]*A(n-i+j-1,j-1)%mod;
            dp[i] %= mod;
        }
    }
    

    这样的算法还是不行,需要优化,优化考虑去拆A这个排列,然后发现:

    还要注意一点的是对于每一个 (i) 最多只能考虑前面 (k) 个数,所以对 (i>k)(dp) 要删去前面的最后一个值。

    #include <bits/stdc++.h>
    #define inf 0x3f3f3f3f
    #define inf64 0x3f3f3f3f3f3f3f3f
    using namespace std;
    const int maxn = 1e7+10;
    typedef long long ll;
    const int mod = 998244353;
    ll dp[maxn],inv[maxn],fac[maxn];
    void init() {
        fac[0] = 1, fac[1] = 1,inv[1] = 1;
        for (int i = 2; i < maxn; i++) {
            fac[i] = fac[i - 1] * i % mod;
            inv[i] = (mod - mod / i) * inv[mod % i] % mod;
        }
        inv[0] = 1;
        for (int i = 1; i < maxn; i++) {
            inv[i] = inv[i - 1] * inv[i] % mod;
        }
    }
    /*
     * 最小的数是0,所以在0和1之间夹很多数,所以1不一定在1这个位置,因为dp[2]也可以从dp[0]转移过来
     */
    int main(){
        init();
        int n,k;
        scanf("%d%d",&n,&k);
        dp[1] = dp[0] = 1;
        for(int i=2;i<=n;i++){
            dp[i] = dp[i-1]*(n-i+2)%mod;
            if(i>k){
                ll res = fac[n-i+k] * inv[n-i] % mod;
                dp[i] = (dp[i] - res * dp[i - k - 1]%mod + mod)%mod;
            }
        }
        printf("%lld
    ",dp[n]);
        return 0;
    }
    /*
    dp[i] = dp[i-1]
          + dp[i-2]*(n-i+1)
          + dp[i-3]*(n-i+1)*(n-i+2)
     dp[1] = dp[0]
     dp[2] = dp[1]
           + dp[0]*(n-1)
     dp[3] = dp[2]
           + dp[1]*(n-2)
           + dp[0]*(n-1)*(n-2)
    */
    
  • 相关阅读:
    vim命令总结
    SQL语句中----删除表数据drop、truncate和delete的用法
    备份数据工具类
    JavaWeb之 JSP:自定义标签
    Android studio安装与配置
    Redis 从入门到放弃
    java相关网址汇总2
    java相关网址汇总1
    校验字符串编码工具类
    java相关网址汇总(myself)
  • 原文地址:https://www.cnblogs.com/EchoZQN/p/14327121.html
Copyright © 2011-2022 走看看