[POJ3046] [USACO2005Nov,Silver] Ant Counting [多重集组合数][dp/生成函数]

[ L i n k \frak{Link} Link]


题意: n \frak{n} n种物品,第 i \frak{i} i种物品有 a i \frak{a_i} ai个。同种物品完全一致。求取 m \frak{m} m个物品的不同取法数。


按照套路, F ( i , j ) \frak{F(i,j)} F(i,j)表示前 i \frak{i} i种物品取了 j \frak{j} j个的取法数。
那么考虑第 i \frak{i} i种物品取了多少个,之前取了多少个。
F ( i , j ) = ∑ F ( i − 1 , p ) , p ∈ [ j − a i , j ] \frak{F(i,j)=\sum F(i-1,p),p\in[j-a_i,j]} F(i,j)=F(i1,p),p[jai,j]
这东西当然可以优化啦。用一个前缀和就可以了。
然而这样它的复杂度也挺高的。要枚举 i , j , p \frak{i,j,p} i,j,p
所以它的复杂度还是炸了。


上面的方法显然产生了很多不必要的计算。
我们发现多个集合除了每个集合里面蚂蚁个数可能不同之外是一样的。
能不能利用之前的结果来优化呀?
我们首先可以分成某个集合选/不选的情况再慢慢讨论。
第二种递推方法就是拆分为某一组至少选一个或者一个都不选。
一个都不选的很显然啦, f ( i , j ) = f ( i − 1 , j ) \frak{f(i,j)=f(i-1,j)} f(i,j)=f(i1,j)
如果某一组至少选一个呢?
因为至少选一个,我们可以先从 f ( i , j − 1 ) \frak{f(i,j-1)} f(i,j1)考虑转移过来。后面应该还要加什么。
考虑多/少了什么。
f ( i , j − 1 ) \frak{f(i,j-1)} f(i,j1)中也包含了之前计算的,第 i \frak{i} i种选了 p \frak{p} p个的情况、
至于更前面的就不必考虑到了,是合法的。只需要考虑第 i \frak{i} i种。

发现如果 f ( i , j − 1 ) \frak{f(i,j-1)} f(i,j1)包含了第 i \frak{i} i种选了 a i \frak{a_i} ai个的情况,那么转移过来这个情况就不可行了。
这种情况显然应该是 f ( i − 1 , j − a i − 1 ) \frak{f(i-1,j-a_i-1)} f(i1,jai1)
f ( i , j − 1 ) \frak{f(i,j-1)} f(i,j1)在第 i \frak{i} i种选了 a i \frak{a_i} ai个的情况下是由 f ( i − 1 , j − a i − 1 ) \frak{f(i-1,j-a_i-1)} f(i1,jai1)转移过来的)

于是要减掉 f ( i − 1 , j − a i − 1 ) \frak{f(i-1,j-a_i-1)} f(i1,jai1)
最后的式子再分类讨论一下就出来了。


#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int mod = 1000000; //*
int T, A, S, B;
int F[1005][10005];
int N[1005];
bool cond;
int main() {
	scanf("%d%d%d%d", &T, &A, &S, &B);
	for (int t, i = 1; i <= A; ++i) {
		scanf("%d", &t);
		++N[t];
	}
	F[0][0] = F[1][0] = 1; //*
	for (int i = 1; i <= T; ++i) {
		cond = !cond;
		for (int j = 1; j <= B; ++j) {
			F[cond][j] = F[cond][j-1] + F[!cond][j];
			if (j>=N[i]+1) F[cond][j] += mod - F[!cond][j-N[i]-1];
			F[cond][j] %= mod;
		}
	}
	int ans = 0;
	for (int j = S; j <= B; ++j) ans += F[cond][j], ans %= mod;
	printf("%d", ans);
	return 0;
}

生成函数:我不会

你可能感兴趣的:(POJ,dp,生成函数,多重集组合数,计数)