POJ 3046 Ant Counting dp 经典多重集合数问题模型 优化递推关系式

Description
Bessie was poking around the ant hill one day watching the ants march to and fro while gathering food. She realized that many of the ants were siblings, indistinguishable from one another. She also realized the sometimes only one ant would go for food, sometimes a few, and sometimes all of them. This made for a large number of different sets of ants!
Being a bit mathematical, Bessie started wondering. Bessie noted that the hive has T (1 <= T <= 1,000) families of ants which she labeled 1…T (A ants altogether). Each family had some number Ni (1 <= Ni <= 100) of ants.
How many groups of sizes S, S+1, …, B (1 <= S <= B <= A) can be formed?
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 with 2 ants: {1,1} {1,2} {1,3} {2,2} {2,3}
5 sets with 3 ants: {1,1,2} {1,1,3} {1,2,2} {1,2,3} {2,2,3}
3 sets with 4 ants: {1,2,2,3} {1,1,2,2} {1,1,2,3}
1 set with 5 ants: {1,1,2,2,3}
Your job is to count the number of possible sets of ants given the data above.
Input
Line 1: 4 space-separated integers: T, A, S, and B
Lines 2…A+1: Each line contains a single integer that is an ant type present in the hive
Output
Line 1: The number of sets of size S…B (inclusive) that can be created. A set like {1,2} is the same as the set {2,1} and should not be double-counted. Print only the LAST SIX DIGITS of this number, with no leading zeroes or spaces.
Sample Input
3 5 2 3
1
2
2
1
3
Sample Output
10
Hint
INPUT DETAILS:
Three types of ants (1…3); 5 ants altogether. How many sets of size 2 or size 3 can be made?
OUTPUT DETAILS:
5 sets of ants with two members; 5 more sets of ants with three members

题目大意:
有t个蚂蚁家族,相同家族中的蚂蚁没有区别,总共有n只蚂蚁,并给出每个家族中蚂蚁的数量,问最多能组成多少个元素个数在给定区间内的集合。

解题思路:
多重集合数问题模型,题目中同一家族中的蚂蚁没有区别,为了不重复计数,同一种类的蚂蚁最好一次性处理好。

定义dp[i][j] := 从前i个家族选择j个蚂蚁的集合数

基本思路:

为了从前 i 个家族中取出 j 个,可以从前i-1个家族中选出 j-k 个,再从第 i 种中选出k个,可以得到如下递推关系:

d p [ i ] [ j ] = ∑ k = 0 m i n ( j , n u m [ i ] ) d p [ i − 1 ] [ j − k ] dp[i][j] = \sum_{k=0}^{min(j, num[i])} dp[i-1][j-k] dp[i][j]=k=0min(j,num[i])dp[i1][jk]
直接计算的话复杂度为O( tb2 ),可以继续优化

优化递推关系式:

dp[i][j] = dp[i - 1][j] + dp[i][j-1] - dp[i-1][j-1-num[i]] ;

状态: dp[i][ j] := 从前i个家族选择j个蚂蚁的集合数
决策: 第i种一个不选或者至少选一个
状态转移:
1、若不选,则为dp[i-1][ j],即j个蚂蚁都从前i-1个家族中选出
2、若至少选一种,则为dp[i][ j-1] - dp[i-1][ j - num[i] - 1],分析如下:

dp[i][ j-1],即从前i种中取出j-1只,相当于提前拿出了一只第i种的蚂蚁放入到了结果集合中,因此可以保证从第i种中选出一只,但由于dp[i][ j-1]本身也包含了从第i种中选出num[i]只的可能性(前提是j-1>=num[i]),然而由于已经提前拿出了一只,所以要减去这种可能性。

复杂度为O( tb )

AC代码:

#include<iostream>
using namespace std;
int num[1001];
#define M 1000000
//题目中没有给出a的取值范围  
//wa了很久 把dp[1001][1001]改为了dp[1001][100001]才AC  -_-||
int dp[1001][100001];//dp[i][j]表示从前i种中选出j个
int main() {
	int t, a, s, b; int tmp;
	while (cin >> t >> a >> s >> b) {
		for (int i = 1; i <= t; i++) num[i] = 0;
		for (int i = 1; i <= a; i++) {
			cin >> tmp; num[tmp]++;
		}
		for (int i = 1; i <= t; i++) {
			dp[i][0] = 1;//一个都不取,方法只有一种
			dp[0][i] = 0;//从0中取大于0的数,自然不可能
		}
		dp[0][0] = 1;//从0中取0,方法就是不取  好像有点牵强 不过还行 哈哈~
		for (int i = 1; i <= t; i++) {
			for (int j = 1; j <= a; j++) {
				if (j - 1 - num[i] >= 0) {
					//在有取余的情况下,要避免减法运算出现负数
					dp[i][j] = (dp[i - 1][j] + dp[i][j - 1] - dp[i - 1][j - 1 - num[i]] + M) % M;
				}
				else dp[i][j] = (dp[i - 1][j] + dp[i][j - 1]) %M;
			}
		}
		/*for (int i = 1; i <= t; i++) {
			for (int j = 1; j <= a; j++) {
				cout << i << " " << j << " " << dp[i][j] << endl;
			}
		}*/
		int ans = 0;
		for (int i = s; i <= b; i++) { ans = (ans + dp[t][i]) % M; }
		cout << ans << endl;
	}
}

还可以使用滚动数组进行空间复杂度上的优化

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值