计数DP

Dp不仅对于求解最优解问题有效,还可以用来求解各种排列组合的个数、概率或者期望之类的计算。

一、划分数:

          有n个毫无区别的物品,将它们划分成不超过m组,求划分方法数模M的余数。

   例如有4个物品,将它们分成3组,则有4种情况:1+1+2,1+3,2+2,4。这样的划分叫做n的m划分。

定义如下:d[i][j]=j的i划分总数

我们可以思考根据这一定义可以得到那些递推关系。

对于j的i划分的所有数值ai:

1.如果ai全部都大于0的话,那么{ai-1}代表了n-m的m划分。也就是说,ai全部大于0的情况数等于n-m的m划分数。

2.如果存在ai==0,那么就对应了n的m-1划分。

综上,我们可以得到:d[i][j]=d[i][j-i]+d[i-1][j]

#include<cstdio>
#include<iostream>
using namespace std;
int n, m;
int d[1200][1200];
int main(){
    scanf("%d%d", &n, &m);
    d[0][0] = 1;
    for (int i = 1; i <= m; i++)
        for (int j = 0; j <= n; j++){
            if(j >= i) d[i][j] = d[i][j - i] + d[i - 1][j];
            else d[i][j] = d[i - 1][j];
    }
    printf("%d", d[m][n]);
    return 0;
}

二、多重集组合数

       有n种物品,第i种物品有ai个。不同种类的物品不能区分,同种类的物品无法区分。从这些物品中选出m个,有多少种取法?首先,同一类的物品显然要一次处理好。

定义:d[i + 1][j]代表从前i种物品中取出j个的组合总数。

从前i件物品中取出j个,那么可以从前i-1件中取出j-k个,再从第i件中取出k个。

递推关系如下:

    d[i + 1][j] = \sum_{k=0}^{min(j,a[i]])}d[i][j - k]

这个递推式的时间复杂度是O(nm^{2}).

但是我们可以把 \sum_{k=0}^{min(j,a[i]])}d[i][j - k]拆开。

 \sum_{k=0}^{min(j,a[i]])}d[i][j - k] = \sum_{k=0}^{min(j-1,a[i]])}d[i][j-1-k]   +     d[i][j]       -         d[i][j-1-ai]

\sum_{k=0}^{min(j-1,a[i]])}d[i][j-1-k] 是从前i种物品中选出j-1个的方案,即d[i+1][j-1]

d[i][j]表示从前i-1个中选出j个的方案。

我们把这个表达式分成两部分去看。

1.d[i][j]表示不从第i种里面选数的情况数。

2.从第i种里面选的情况数

\sum_{k=0}^{min(j-1,a[i]])}d[i][j-1-k]  - d[i][j-1-ai]

其中\sum_{k=0}^{min(j-1,a[i]])}d[i][j-1-k]表示从前i种选了j-1个的情况数,并且已经从第i种已经选了1个。然而它包含了从第i种里面选a[i]个数的情况,所以需要把d[i+1][j-1]中选择了a[i]个第i种的方案数删掉,即d[i][j-1-a[i]]。

#include<iostream>
using namespace std;
#define MOD 1000000
int n, m;
int ant[1005];
int dp[2][100000];
int ans;
int main()
{
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++)
	{
		int x;
		scanf("%d", &x);
		a[x]++;
	}
	for (int i = 1; i <= n; i++) d[i][0] = 1;
	for (int i = 0; i < n; i++)
		for (int j = 1; j <= m; j++)
			if (j - a[i] - 1 >= 0) dp[i + 1][j] = (dp[i + 1][j - 1] + dp[i][j] - dp[i][j - ant[i] - 1] + MOD) % MOD;      //在取模时若出现了减法运算则需要先+Mod再对Mod取模,防止出现负数(如5%4-3%4为负数)
			else dp[i + 1][j] = (dp[i + 1][j - 1] + dp[i][j ]) % MOD;
	printf("%d\n", dp[n][m]);
	return 0;
}

 

 

  

 

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值