题目链接:
题目大意:
给出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 );
}
}