zoukankan      html  css  js  c++  java
  • POJ 3046 Ant Counting ( 多重集组合数 && 经典DP )

    题意 : 有 n 种蚂蚁,第 i 种蚂蚁有ai个,一共有 A 个蚂蚁。不同类别的蚂蚁可以相互区分,但同种类别的蚂蚁不能相互区别。从这些蚂蚁中分别取出S,S+1...B个,一共有多少种取法。

    分析 : 

    实际就是要解决 => 从 n 种物品中取出 m 个有多少种取法 ( 同种无法区分 )

    计数问题的 DP 定义必须保证不重复计数

    这里定义 dp[i+1][j] => 从前 i 种物品中取出 j 个的组合数

    根据定义为了从前 i 种物品中取出 j 个,可以从前 i-1 中取出 j-k 个并从 i 种中取出 k 个

    即 dp[i+1][j] = ∑dp[i][j-k] 【 0 ≤ k ≤ min(j, ant[i]) 】

    但是这个递推式的求和太耗时间,实际可以优化,考虑两种情况 j ≤ ant[i] 和 j > ant[i]

    ① j ≤ ant[i] ( 即 j-1 < ant[i] )

    此时 ∑ 的上界 min( j, ant[i] ) = j ,将式子展开有 dp[i][0]+dp[i][1]+dp[i][2]...dp[i][j] ( k 从大到小枚举 )

    将展开式的 dp[i][j] 取出来那么将得到 ∑dp[i][j-1-k] 【 0 ≤ k ≤ j-1 】( 其实这个就是 dp[i+1][j-1] !!! )

    那么最后 dp[i+1][j] = dp[i+1][j-1] + dp[i][j]

    ② j > ant[i]

    此时 ∑ 的上界 min( j, ant[i] ) = ant[i],将式子展开有 dp[i][j-ant[i]]+dp[i][j-ant[i]+1]...dp[i][j]

    对比 ① 的结果,很明显如果用 ① 的结果 - dp[i][j-ant[i]-1] 就能得到上面的展开式了!

    所以 ② 的情况下,dp[i+1][j] = ( dp[i+1][j-1] + dp[i][j] ) - dp[i][j-ant[i]-1]

    #include<stdio.h>
    #include<string.h>
    using namespace std;
    const int mod = 1000000;
    int dp[1010][100010];
    int num[1010];
    int main(void)
    {
        int T, A, S, B;
        while(~scanf("%d %d %d %d", &T, &A, &S, &B)){
    
            memset(num, 0, sizeof(num));
            for(int temp,i=1; i<=A; i++)
                scanf("%d", &temp),
                num[temp-1]++;
    
            for(int i=0; i<=T; i++)
                dp[i][0] = 1;
    
            for(int i=0; i<T; i++){
                for(int j=1; j<=B; j++){
                    if(j - 1 - num[i] >= 0)
                        dp[i+1][j] = (dp[i+1][j-1] + dp[i][j] - dp[i][j-1-num[i]] + mod)%mod;
                    else
                        dp[i+1][j] = (dp[i+1][j-1] + dp[i][j])%mod;
                }
            }
    
            int ans = 0;
            for(int i=S; i<=B; i++)
                ans = (ans + dp[T][i])%mod;
            printf("%d
    ", ans);
        }
        return 0;
    }
    View Code

    其实 DP 的阶段 ( 数组第一维 ) 只跟前一个有关系,故用滚动数组优化

    #include<stdio.h>
    #include<string.h>
    using namespace std;
    const int mod = 1000000;
    int dp[2][100010];
    int num[1010];
    int main(void)
    {
        int T, A, S, B;
        while(~scanf("%d %d %d %d", &T, &A, &S, &B)){
    
            memset(num, 0, sizeof(num));
            for(int temp,i=1; i<=A; i++)
                scanf("%d", &temp),
                num[temp-1]++;
    
    
            int idx = 0;
            dp[idx][0] = dp[idx^1][0] = 1;
            for(int i=0; i<T; i++,idx^=1){
                for(int j=1; j<=B; j++){
                    if(j - 1 - num[i] >= 0)
                        dp[idx^1][j] = (dp[idx^1][j-1] + dp[idx][j] - dp[idx][j-1-num[i]] + mod)%mod;
                    else
                        dp[idx^1][j] = (dp[idx^1][j-1] + dp[idx][j])%mod;
                }
            }
    
            int ans = 0;
            for(int i=S; i<=B; i++)
                ans = (ans + dp[idx][i])%mod;
            printf("%d
    ", ans);
        }
        return 0;
    }
    View Code
  • 相关阅读:
    亲历dataguard的一些经验问答题
    [转]ORA-38500: USING CURRENT LOGFILE option not available without stand
    修改npm全局安装模式的路径
    Vue 环境搭建
    Linux下查看系统版本号信息的方法
    每天一个Linux命令(12):su命令
    Ubuntu 首次给root用户设置密码
    适用于Linux的windows子系统
    IDEA的terminal设置成Linux的终端一样
    Windows模拟linux终端工具Cmder+Gow
  • 原文地址:https://www.cnblogs.com/qwertiLH/p/8143884.html
Copyright © 2011-2022 走看看