poj 3088 Push Botton Lock(斯特林数+组合数学)

题目链接:

点击打开链接

题目大意:

给出1...n的n个数,问能够有多少中划分方法(可以不全部用到)

题目分析:

首先就是枚举要取出的数的个数,然后对应某个值i会有C(n,i)中取法,然后对于选出的i个数,再枚举分成的集合数j,那么对于每个j,正好是对于有区分的i个元素划分成j个集合,然后再乘上j个元素的全排列数,(因为集合的排列位置不同也不算同一种)正好是第二类斯特林数的定义,所以直接先把组合数和斯特林数的表打出来,组合数打表方法就不多说了,

对于斯特林数的打表,就是s[i][j] = s[i-1][j-1] + j*s[i-1][j] ,其中i表示可区分的元素个数,j表示要划分成的集合数,那么也就是通过把i个可区分元素放入j个盒子中,就相当于在i-1个元素分成j-1个盒子情况下,新添加的元素新成为一个集合,还有一种是之前的i-1个元素已经分成了j个集合,那么挑一个集合将新元素放进去。这样递推就能推出整张斯特林数的表了,然后对于每次查询算一下就好了,复杂度n^2,而n只有10几

代码如下:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#define MAX 20

using namespace std;

typedef long long LL;
LL s[MAX][MAX];
LL c[MAX][MAX];
LL a[MAX];

void init ( )
{
    c[0][0] = 1;
    for ( int i = 1 ; i < 15 ; i++ )
        for ( int j = 0 ; j <=i ; j++ )
            if ( j == 0 || j == i )
                c[i][j] = 1;
            else c[i][j] = c[i-1][j-1] + c[i-1][j];
    s[1][1] = 1;
    s[1][0] = 0;
    for ( int i = 2 ; i < 15 ; i++ )
        for ( int j = 0 ; j <= i ; j++ )
            if ( j == 0 )
                s[i][j] = 0;
            else if ( j == i )
                s[i][j] = 1;
            else s[i][j] = s[i-1][j-1] + j*s[i-1][j];
    a[0] = 1;
    for ( int i = 1 ; i < MAX ; i++ )
        a[i] = a[i-1]*i;
}

int t,n;

int main ( )
{
    scanf ( "%d" , &t );
    init();
    int cc = 1;
    while ( t-- )
    {
        scanf ( "%d" , &n );
        LL ans = 0;
        for ( int i = 1 ; i <= n ; i++ )
            for ( int j = 1 ; j <= i ; j++ )
                ans += c[n][i]*s[i][j]*a[j];
        printf ( "%d %d %lld\n" , cc++ , n , ans );
    }
}




你可能感兴趣的:(C++,组合数学,第二类斯特林数)