【HDU 1521】排列组合 母函数

                                          排列组合

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;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值