1. 程式人生 > >[POJ3046] [USACO2005Nov,Silver] Ant Counting [多重集組合數][dp/生成函式]

[POJ3046] [USACO2005Nov,Silver] Ant Counting [多重集組合數][dp/生成函式]

[ L i n k \frak{Link} ]


題意: n

\frak{n} 種物品,第 i \frak{i} 種物品有 a i
\frak{a_i}
個。同種物品完全一致。求取 m \frak{m} 個物品的不同取法數。


按照套路, F (

i , j ) \frak{F(i,j)} 表示前 i \frak{i} 種物品取了 j \frak{j} 個的取法數。
那麼考慮第 i \frak{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]}
這東西當然可以優化啦。用一個字首和就可以了。
然而這樣它的複雜度也挺高的。要列舉 i , j , p \frak{i,j,p}
所以它的複雜度還是炸了。


上面的方法顯然產生了很多不必要的計算。
我們發現多個集合除了每個集合裡面螞蟻個數可能不同之外是一樣的。
能不能利用之前的結果來優化呀?
我們首先可以分成某個集合選/不選的情況再慢慢討論。
第二種遞推方法就是拆分為某一組至少選一個或者一個都不選。
一個都不選的很顯然啦, f ( i , j ) = f ( i 1 , j ) \frak{f(i,j)=f(i-1,j)}
如果某一組至少選一個呢?
因為至少選一個,我們可以先從 f ( i , j 1 ) \frak{f(i,j-1)} 考慮轉移過來。後面應該還要加什麼。
考慮多/少了什麼。
f ( i , j 1 ) \frak{f(i,j-1)} 中也包含了之前計算的,第 i \frak{i} 種選了 p \frak{p} 個的情況、
至於更前面的就不必考慮到了,是合法的。只需要考慮第 i \frak{i} 種。

發現如果 f ( i , j 1 ) \frak{f(i,j-1)} 包含了第 i \frak{i} 種選了 a i \frak{a_i} 個的情況,那麼轉移過來這個情況就不可行了。
這種情況顯然應該是 f ( i 1 , j a i 1 ) \frak{f(i-1,j-a_i-1)}
f ( i , j 1 ) \frak{f(i,j-1)} 在第 i \frak{i} 種選了 a i \frak{a_i} 個的情況下是由 f ( i 1 , j a i 1 ) \frak{f(i-1,j-a_i-1)} 轉移過來的)

於是要減掉 f ( i 1 , j a i 1 ) \frak{f(i-1,j-a_i-1)}
最後的式子再分類討論一下就出來了。


#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<cctype>
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;
}

生成函式:我不會