(经典)POJ-3046 多重集组合数

题目大意:蚂蚁牙黑,蚂蚁牙红:有A只蚂蚁,来自T个家族,分别记为ant[i]个。同一个家族的蚂蚁长得一样,但是不同家族的蚂蚁牙齿颜色不同。任取n只蚂蚁(S <= n <= B),求能组成几种集合?


题目链接:点击打开链接


分析:

多重集组合数也是由多重背包问题拓展出来的一类经典问题。这里仍然给大家讲2种方法:

①朴素方法:

状态:dp[i][j]:前i种中选j个可以组成的种数

决策:i种选k个,k<=ant[i] && j-k>=0

转移:dp[i][j]=Σdp[i-1][j-k]

复杂度为O(B*Σant[i])即O(B*A)也即O(A^2),虽说这题A最大可到1e5,但是实际数据水,能过


②优化递推式

状态:dp[i][j]:前i种中选j个可以组成的种数

决策:i种不选或者至少选一个

转移:

1.若不选,显然为dp[i-1][j]

2.若至少选一种,那么为dp[i][j-1]-dp[i-1][j-ant[i]-1]

我们这样来理解,dp[i][j-1] 理解为已经选了第i种一个,至于还选不选这里我们不管它,所以它可以用来代表至少选一个

但是dp[i][j-1]还有一层含义便是前i种中选j-1个可以组成的种数,所以它包含了选ant[i]个第i种,即dp[i-1][j-ant[i]-1],但

dp[i][j] 最多选ant[i]个第i种,所以最后要减去这一种。

所以 dp[i][j] = dp[i-1][j] + dp[i][j-1] - dp[i-1][j-ant[i]-1]

复杂度为O(T*B)


仅附上优化过的代码:

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值