排列组合
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 5474 Accepted Submission(s): 2398
Problem Description
有n种物品,并且知道每种物品的数量。要求从中选出m件物品的排列数。例如有两种物品A,B,并且数量都是1,从中选2件物品,则排列有"AB","BA"两种。
Input
每组输入数据有两行,第一行是二个数n,m(1<=m,n<=10),表示物品数,第二行有n个数,分别表示这n件物品的数量。
Output
对应每组数据输出排列数。(任何运算不会超出2^31的范围)
Sample Input
2 2
1 1
Sample Output
2
题意:就是有 n 种物品,每种物品都有一定的件数,从中选取 m 件,一共有多少种选法,多重集排列问题。
题解:我们相当于用 n 种物品中的一些物品来凑出这 m 件物品,对于第 i 种物品 ,我们可以用一个表达式来表示选取方式,
num = Xi ^ 0 + Xi ^ 1 + Xi ^ 2 + ... Xi ^ ti ( t 表示第 i 件物品最多 ti 件,指数代表选取了多少件,0 代表不选,1 选一件,) ,但是这样选取出来会重复,比如 aaaxyz,我们会计算 6 次,但是仅有一次。
n个元素,其中a1,a2,····,an互不相同,进行全排列,可得n!个不同的排列。
若其中某一元素ai重复了ni次,全排列出来必有重复元素,其中真正不同的排列数应为 n ! / ni !,即其重复度为ni!
同理a1重复了n1次,a2重复了n2次,····,ak重复了nk次,n1+n2+····+nk=n。
对于这样的n个元素进行全排列,可得不同排列的个数实际上是 n ! / ( n1 !*n2 ! * n3 !*...*nk !)。
所以我们要除以每种物品选取的件数的重复度。所以我们所推出的母函数为
F(x) = (X1^ 0 / 0! + X1 ^ 1 / 1! + X1 ^ 2 / 2! + ... X1 ^ t1 / t1!) * (X2^ 0 / 0! + X2 ^ 1 / 1! + X2 ^ 2 / 2! + ... X2 ^ t2 / t2!) *...* (Xn^ 0 / 0! + Xn ^ 1 / 1! + Xn ^ 2 / 2! + ... Xn ^ tn / tn!) 最后我们要需要的是指数为 m 的系数,然后还要乘以 m 的阶乘,因为在选取物品时是有先后顺序的,即选取的 m 件物品的一个全排列。
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn = 17;
int a[maxn];
double b[maxn],c[maxn];
int n,m;
double fact(int num) //阶乘
{
double ret = 1.0;
for( int i = 1; i <= num; i++) ret *= i;
return (double)ret;
}
int main()
{
int i,j,k;
while(scanf("%d %d",&n,&m)==2){
memset(b, 0, sizeof(b));
memset(c, 0, sizeof(c));
for( i = 1; i <= n; i++) scanf("%d",&a[i]);
for( i = 1; i <= n; i++) {
if(a[i] != 0){
for( j = 0; j <= a[i] && j <= m; j++){
b[j] = 1.0/fact(j);
}
break;
}
}
for( i = i+1; i <= n; i++){
if(a[i] != 0){
for( k = 0; k <= m; k++){
if(b[k] != 0){
for( j = 0; j <= a[i] && j+k <= m ; j++) c[j+k] += 1.0/fact(j)*b[k];
}
}
for( k = 0; k <= m; k++){
b[k] = c[k];
c[k] = 0;
}
}
}
printf("%.0f\n",b[m]*fact(m));
}
return 0;
}