Bell数

Bell数的定义:第n个Bell数表示集合{1,2,3,...,n}的划分方案数,即:B[0] = 1;

 

 

每一个Bell数都是第二类Stirling数的和,即:

 

 

第二类Stirling数的意义是:S(n,k)表示将n个物体划分成k个非空的不可辨别的(可以理解为盒子没有编号)集合的方法

数。很明显,每一个Bell是对应的第二类Stirling数之和。

 

Bell数的指数生成函数是:

 

 

关于它的推导过程详见:http://ftiasch.github.io/useless/posts/2013-09-27-generating-function-of-bell-number.html

 

 

 

Bell三角形(构建方法类似于杨辉三角形)

 

Bell三角形的构造方法:

第一行第一个元素是1,即a[1][1] = 1

对于n>1,第n行第一项等于第n-1行最后一项,即a[n][1] = a[n-1][n-1];

对于m,n>1,第n行第m项等于它左边和左上方的两个数之和,即a[n][m] = a[n][m-1] + a[n-1][m-1];

 

如图:

 

可以看出,每行首项是贝尔数,每行之和是第二类Stirling数。

 

 

Bell还有两个重要的同余性质:

 

 

 

其中这里的p是不大于100的素数,这样,我们可以通过上面的性质来计算Bell数模小于100的素数值。

 

Bell数模素数p的周期为:

 

 

 

典型题目:HDU2512,HDU4767

 

Bell数的预处理:

void Bell(int T[],int MOD)
{
    B[0] = 1;
    B[1] = 1;
    T[0] = 1;
    for(int i=2;i<N;i++)
    {
        T[i-1] = B[i-1];
        for(int j=i-2;j>=0;j--)
            T[j] = (T[j]+T[j+1])%MOD;
        B[i] = T[0];
    }
}


 

题目:http://acm.hdu.edu.cn/showproblem.php?pid=4767

 

题意:给定一个数n,范围是[1,2^31],求Bell(n)(mod 95041567)

 

分析:注意95041567 = 31x37x41x43x47,那么我们先对每一个素数求出Bell(n)(mod p),然后CRT合并即可。

在这里,我们有两种方法,第一种方法就是用

 

 

所以可以根据它构造50*50的矩阵。如图:

 

 

 

#include <iostream>
#include <string.h>
#include <stdio.h>

using namespace std;
const int N = 50;

int a[5];
int m[5] = {31,37,41,43,47};
int B[5][N],T[N];

void Bell(int T[],int index,int MOD)
{
    B[index][0] = 1;
    B[index][1] = 1;
    T[0] = 1;
    for(int i=2;i<N;i++)
    {
        T[i-1] = B[index][i-1];
        for(int j=i-2;j>=0;j--)
            T[j] = (T[j]+T[j+1])%MOD;
        B[index][i] = T[0];
    }
}

struct Matrix
{
    int m[N][N];
};

Matrix I;

Matrix multi(Matrix a,Matrix b,int MOD)
{
    int i,j,k;
    Matrix c;
    for(i=0;i<N;i++)
    {
        for(j=0;j<N;j++)
        {
            c.m[i][j] = 0;
            for(k=0;k<N;k++)
                c.m[i][j] = (c.m[i][j] + a.m[i][k]%MOD * b.m[k][j]%MOD)%MOD;
            c.m[i][j] %= MOD;
        }
    }
    return c;
}

Matrix power(Matrix A,int k,int MOD)
{
    Matrix ans = I,p = A;
    while(k)
    {
        if(k&1)
        {
            ans = multi(ans,p,MOD);
            k--;
        }
        k>>=1;
        p = multi(p,p,MOD);
    }
    return ans;
}

void extend_Euclid(int a,int b,int &x,int &y)
{
    if(b==0)
    {
        x = 1;
        y = 0;
        return;
    }
    extend_Euclid(b,a%b,x,y);
    int tmp = x;
    x = y;
    y = tmp - (a/b)*y;
}

int CRT(int a[],int m[],int n)
{
    int x,y;
    int Mi,M=1,ans = 0;
    for(int i=0;i<n;i++)
        M *= m[i];
    for(int i=0;i<n;i++)
    {
        Mi = M/m[i];
        extend_Euclid(Mi,m[i],x,y);
        ans = (ans + Mi*x*a[i])%M;
    }
    if(ans < 0) ans += M;
    return ans;
}

void Init()
{
    for(int i=0;i<5;i++)
       Bell(T,i,m[i]);
    for(int i=0;i<N;i++)
       for(int j=0;j<N;j++)
           I.m[i][j] = (i == j);
}

void Work(int n)
{
    if(n < N)
    {
        for(int i=0;i<5;i++)
            a[i] = B[i][n];
        int ans = CRT(a,m,5);
        printf("%d\n",ans);
    }
    else
    {
        Matrix A;
        for(int i=0;i<5;i++)
        {
            for(int k=0;k<N;k++)
               for(int j=0;j<N;j++)
                  A.m[k][j] = 0;
            for(int j=0;j<m[i]-1;j++)
               A.m[j+1][j] = 1;
            A.m[0][m[i]-1] = A.m[1][m[i]-1] = 1;
            Matrix ans = power(A,n - m[i] + 1,m[i]);
            a[i] = 0;
            for(int j=0;j<m[i];j++)
                a[i] = (a[i] + B[i][j]%m[i]*ans.m[j][m[i]-1]%m[i])%m[i];
            a[i] %= m[i];
        }
        int ret = CRT(a,m,5);
        printf("%d\n",ret);
    }
}

int main()
{
    Init();
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int n;
        scanf("%d",&n);
        Work(n);
    }
    return 0;
}


 

 

第二种方法就是利用公式:

 

 

思路:我们求Bell(n)(mod p),那么先把n写成p进制,即:

 

 

先预处理b[i] = Bell(i)(mod p),然后对于大数n求Bell(n)(mod p)就很容易写出代码:

 

for i = 0 to p
    c[i] = b[i];
for i = 1 to cnt-1
    begin
    for j = 1 to a[i] do
        begin
        for k = 0 to p-1
	    d[k] = (c[k+1] + i*c[k]) mod p;
        d[p] = (d[1] + d[0]) mod p;
        for k = 0 to p
	    c[k] = d[k];
	end
    end



那么,d[a[0]]的值就是Bell(n)(mod p)

 

#include <iostream>
#include <string.h>
#include <stdio.h>

using namespace std;
const int N = 50;

int a[5];
int m[5] = {31,37,41,43,47};
int B[5][N],T[N];

void Bell(int T[],int index,int MOD)
{
    B[index][0] = 1;
    B[index][1] = 1;
    T[0] = 1;
    for(int i=2;i<N;i++)
    {
        T[i-1] = B[index][i-1];
        for(int j=i-2;j>=0;j--)
            T[j] = (T[j]+T[j+1])%MOD;
        B[index][i] = T[0];
    }
}

int Solve(int n,int index)
{
    int p = m[index];
    int a[N],c[N],d[N];
    for(int i=0;i<=p;i++)
        c[i] = B[index][i];
    int cnt = 0;
    while(n)
    {
        a[cnt++] = n%p;
        n /= p;
    }
    for(int i=1;i<cnt;i++)
    {
        for(int j=1;j<=a[i];j++)
        {
            for(int k=0;k<p;k++)
               d[k] = (c[k+1] + i*c[k])%p;
            d[p] = (d[0] + d[1])%p;
            for(int k=0;k<=p;k++)
               c[k] = d[k];
        }
    }
    return d[a[0]];
}


void extend_Euclid(int a,int b,int &x,int &y)
{
    if(b==0)
    {
        x = 1;
        y = 0;
        return;
    }
    extend_Euclid(b,a%b,x,y);
    int tmp = x;
    x = y;
    y = tmp - (a/b)*y;
}

int CRT(int a[],int m[],int n)
{
    int x,y;
    int Mi,M=1,ans = 0;
    for(int i=0;i<n;i++)
        M *= m[i];
    for(int i=0;i<n;i++)
    {
        Mi = M/m[i];
        extend_Euclid(Mi,m[i],x,y);
        ans = (ans + Mi*x*a[i])%M;
    }
    if(ans < 0) ans += M;
    return ans;
}

void Init()
{
    for(int i=0;i<5;i++)
       Bell(T,i,m[i]);
}

void Work(int n)
{
    if(n<50)
    {
        for(int i=0;i<5;i++)
           a[i] = B[i][n];
        int ans = CRT(a,m,5);
        printf("%d\n",ans);
    }
    else
    {
        for(int i=0;i<5;i++)
           a[i] = Solve(n,i);
        int ans = CRT(a,m,5);
        printf("%d\n",ans);
    }
}

int main()
{
    Init();
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int n;
        scanf("%d",&n);
        Work(n);
    }
    return 0;
}


 

  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值