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\n", ans);
    }
    return 0;
}

其实 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\n", ans);
    }
    return 0;
}

原文地址:https://www.cnblogs.com/Rubbishes/p/8143884.html

时间: 2024-11-08 19:33:17

POJ 3046 Ant Counting ( 多重集组合数 && 经典DP )的相关文章

poj 3046 Ant Counting 多项式乘法解可重组合数

题意: 裸的求可重组合数. 分析: 多项式乘法求系数的应用啊,不用dp. 代码: //poj 3046 //sep9 #include <iostream> using namespace std; const int maxN=1024; const int maxL=100024; const int mod=1000000; int num[maxN]; int coef[maxL]; int tmp[maxL]; int l1; void mul(int l2) { for(int i

poj 3046 Ant Counting DP

大致题意:给你a个数字,这些数字范围是1到t,每种数字最多100个,求问你这些a个数字进行组合(不包含重复),长度为s到b的集合一共有多少个. 思路:d[i][j]——前i种数字组成长度为j的集合有多少个. 那么,当前考虑第i种数字,我要组成长度为j的集合,只用在前i-1种数字所组成的集合中,只要添加0...cnt[i]个第i种数字之后长度能够达到j的那些集合数加起来 所以方程可以写成d[i][j] = ∑(cnt[i],0)  d[i-1][j-k]. 每种数字最多100个,数字最大为1000

POJ 3046 Ant Counting(“动态规划” 优化递推关系式)

http://poj.org/problem?id=3046 蚂蚁牙黑,蚂蚁牙红:有A只蚂蚁,来自T个家族.同一个家族的蚂蚁长得一样,但是不同家族的蚂蚁牙齿颜色不同.任取n只蚂蚁(S<=n<=B),求能组成几种集合? 这是<2.3 记录结果再利用的"动态规划" 优化递推关系式>练习题的第二题. 定义 dp[i][j] := 使用前i个家族可以配出来"元素个数为j"的集合的个数. 那么dp[0][0] = 1,不使用任何蚂蚁配出空集的个数为1.

poj 3046 Ant Counting

题目大意: 有编号一到t的蚂蚁家族,每个家族有不同的蚂蚁数. 问构成S只蚂蚁到构成B只蚂蚁共有多少种方式 例如 While observing one group, the set of three ant families was seen as {1, 1, 2, 2, 3}, though rarely in that order. The possible sets of marching ants were: 3 sets with 1 ant: {1} {2} {3} 5 sets

POJ 1160:Post Office 邮局经典DP

Post Office Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 17168   Accepted: 9270 Description There is a straight highway with villages alongside the highway. The highway is represented as an integer axis, and the position of each villa

HDU - 1003 - Max Sum &amp;&amp; POJ - 1050 - To the Max (经典DP问题)

题目传送:HDU - 1003 思路:最大子序列和 dp[i]= a[i]   (dp[i-1]<0) dp[i]= dp[i-1]+a[i]   (dp[i-1]>=0) AC代码: #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #include <cmath> #include <queue> #include

多重集组合数 简单dp

1 #include <cstdio> 2 #include <iostream> 3 4 using namespace std; 5 6 const int max_n = 1000+2; 7 const int max_m = 1000+2; 8 const int max_a = 1000+2; 9 const int max_M = 1e4+2; 10 11 int n,m,M; 12 int a[max_n]; 13 int dp[max_M][max_M]; 14 /

Ant Counting (poj 3046 分组背包)

Language: Default Ant Counting Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 3142   Accepted: 1246 Description Bessie was poking around the ant hill one day watching the ants march to and fro while gathering food. She realized that man

计数DP(划分数,多重集组合数)

划分数:把n个无区别的物品划分成不超过m组. dp[i][j]=j的i划分的总数. dp[i[j]=dp[i][j-i]+dp[i-1][j] 即:将j个物品分成i份,有两种情况:每份划分都大于等于1 dp[i][j-i]; 存在有一份以上用0划分dp[i-1][j] int main() { int n,m; cin>>n>>m; dp[0][0]=1; for(int i=1;i<=n;i++) for(int j=0;j<=n;j++) { if(j>=i)