hdu477 Bell——求第n项贝尔数

题意

设第 $n$ 个Bell数为 $B_n$,求 $B_n \ mod  \ 95041567$.($1 \leq  n  \leq  2^{31}$)

分析

贝尔数的概念和性质,维基百科上有,这里用到两点。

  • 若 $p$ 是任意素数,有 $B_{p+n} = B_n + B_{n+1}(mod \ p)$
  • 每个贝尔数都是相应第二类斯特林数之和,即 $B_n = \sum_{k=1}^nS(n, k)$

贝尔数的这个递推式只适合质数,我们可以将模数拆成质数,然后用CRT合并。

$95041567 = 31 \times 37 \times 41 \times 43 \times 47$,所以预处理前50个,

对于 $n > 50$,使用递推式,递推式可转成矩阵乘法,如下:

$$\begin{bmatrix} 0 & 0 & \cdots  & 1\\
1 & 1 & \cdots & 0\\  \vdots & \vdots &\ddots   & \vdots\\  0 & \cdots & 1 & 1 \end{bmatrix} \times
\begin{bmatrix} B_n\\  B_{n+1}\\  \vdots\\  B_{n+p-1} \end{bmatrix} =
\begin{bmatrix} B_{n+p-1}\\  B_{n+p}\\  \vdots \\  B_{n+2p-2} \end{bmatrix}$$

即 $B_{n+p-1} = A  B_n$

设 $t = n / (p-1), k = n \% (p-1)$,

如果利用 $B_n = A^tB_k$,需要多预处理一倍,但计算时只需求第一个元素;

若利用 $B_{(p-1)t} = A^t B_0$,只需预处理前 $p-1$ 个,但是计算时需要算出第 $k$ 个。

反正两者时间也几乎一样。

时间复杂度为 $O(5\cdot p^3 log{\frac{n}{p}})$

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn = 50;
const int mod = 95041567;

int m[5] = {31, 37, 41, 43, 47};
int Sti[2*maxn][2*maxn], bell[5][2*maxn];  //第二类司特林数、贝尔数

void Stirling2()
{
    Sti[0][0] = 1;
    for(int i = 0;i <= 2*maxn;i++)
        for(int j = 1;j <= i;j++)
            Sti[i][j] = (Sti[i-1][j-1] + 1LL * j * Sti[i-1][j]) % mod;
}

void init()
{
    Stirling2();
    for(int i = 0;i < 5;i++)
    {
        bell[i][0] = 1;
        for(int j = 1;j <= 2*m[i];j++)
        {
            bell[i][j] = 0;     //不知道为什么默认不是0
            for(int k = 1;k <= j;k++)
                bell[i][j] = (bell[i][j] + Sti[j][k]) % m[i];
            //printf("%d\t%d\n",j,bell[i][j]);
        }
    }
}

struct matrix
{
    int r, c;
    int mat[maxn][maxn];
    matrix(){
        memset(mat, 0, sizeof(mat));
    }
};

matrix mul(matrix A, matrix B, int p)   //矩阵相乘
{
    matrix ret;
    ret.r = A.r; ret.c = B.c;
    for(int i = 0;i < A.r;i++)
        for(int k = 0;k < A.c;k++)
            for(int j = 0;j < B.c;j++)
            {
                ret.mat[i][j] = (ret.mat[i][j] + 1LL * A.mat[i][k] * B.mat[k][j]) % p;
            }
    return ret;
}

matrix mpow(matrix A, int n, int p)
{
    matrix ret;
    ret.r = A.r; ret.c = A.c;
    for(int i = 0;i < ret.r;i++)  ret.mat[i][i] = 1;
    while(n)
    {
        if(n & 1)  ret = mul(ret, A, p);
        A = mul(A, A, p);
        n >>= 1;
    }
    return ret;
}

int solve(int n, int p, int k)  //计算Bn % p
{
    matrix A;
    A.r = A. c = p;
    A.mat[0][p-1] = 1;
    for(int i = 1;i < p;i++)
        A.mat[i][i-1] = A.mat[i][i] = 1;

    matrix tmp = mpow(A, n/(p-1), p);

    int ret = 0;
    for(int i = 0;i < p;i++)
        ret = (ret + tmp.mat[0][i] * bell[k][(n%(p-1))+i]) % p;
    return ret;}

//ax + by = d,且|x|+|y|最小,其中d=gcd(a,b)
//即使a, b在int范围内,x和y也有可能超过int范围
void exgcd(ll a, ll b, ll &d, ll &x, ll &y)
{
    if (!b){ d = a; x = 1; y = 0;}
    else{ exgcd(b, a % b, d, y, x); y -= x * (a / b);}
}

//n个方程:x=a[i](mod m[i])
ll china(int n, int* a, int* m)
{
    ll M = 1, d, y, x = 0;
    for (int i = 0; i < n; i++)  M *= m[i];
    for (int i = 0; i < n; i++)
    {
        ll w = M / m[i];
        exgcd(m[i], w, d, d, y);        //d共用了
        x = (x + y * w * a[i]) % M;   //x相当于sum
    }
    return (x + M) % M;
}

int n;
int res[5];

int main()
{
    init();

    int T;
    scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &n);
        for(int i = 0;i < 5;i++)  res[i] = solve(n, m[i], i);

//        for(int i = 0;i < 5;i++)  printf("%d ", res[i]);
//        printf("\n");

        int ans = china(5, res, m);
        printf("%d\n", ans);
    }
    return 0;
}

第二种种写法:

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn = 50;
const int mod = 95041567;

int m[5] = {31, 37, 41, 43, 47};
int Sti[maxn][maxn], bell[5][maxn];  //第二类司特林数、贝尔数

void Stirling2()
{
    Sti[0][0] = 1;
    for(int i = 0;i <= maxn;i++)
        for(int j = 1;j <= i;j++)
            Sti[i][j] = (Sti[i-1][j-1] + 1LL * j * Sti[i-1][j]) % mod;
}

void init()
{
    Stirling2();
    for(int i = 0;i < 5;i++)
    {
        bell[i][0] = 1;
        for(int j = 1;j <= m[i];j++)
        {
            bell[i][j] = 0;
            for(int k = 1;k <= j;k++)
                bell[i][j] = (bell[i][j] + Sti[j][k]) % m[i];
            //printf("%d\t%d\n",j,bell[i][j]);
        }
    }
}

struct matrix
{
    int r, c;
    int mat[maxn][maxn];
    matrix(){
        memset(mat, 0, sizeof(mat));
    }
};

matrix mul(matrix A, matrix B, int p)   //矩阵相乘
{
    matrix ret;
    ret.r = A.r; ret.c = B.c;
    for(int i = 0;i < A.r;i++)
        for(int k = 0;k < A.c;k++)
            for(int j = 0;j < B.c;j++)
            {
                ret.mat[i][j] = (ret.mat[i][j] + 1LL * A.mat[i][k] * B.mat[k][j]) % p;
            }
    return ret;
}

matrix mpow(matrix A, int n, int p)
{
    matrix ret;
    ret.r = A.r; ret.c = A.c;
    for(int i = 0;i < ret.r;i++)  ret.mat[i][i] = 1;
    while(n)
    {
        if(n & 1)  ret = mul(ret, A, p);
        A = mul(A, A, p);
        n >>= 1;
    }
    return ret;
}

int solve(int n, int p, int k)  //计算Bn % p
{
    matrix A;
    A.r = A. c = p;
    A.mat[0][p-1] = 1;
    for(int i = 1;i < p;i++)
        A.mat[i][i-1] = A.mat[i][i] = 1;

    matrix tmp = mpow(A, n/(p-1), p);

  //  int ret = 0;
//    for(int i = 0;i < p;i++)
//        ret = (ret + A.mat[0][i] * bell[k][i]) % p;

    int ans[p];
    for(int i = 0;i < p;i++)
    {
        ans[i] = 0;
        for(int j = 0;j < p;j++)
            ans[i] = (ans[i] + 1LL * bell[k][j] * tmp.mat[i][j]) % p;
    }

    return ans[n % (p-1)];
}

//ax + by = d,且|x|+|y|最小,其中d=gcd(a,b)
//即使a, b在int范围内,x和y也有可能超过int范围
void exgcd(ll a, ll b, ll &d, ll &x, ll &y)
{
    if (!b){ d = a; x = 1; y = 0;}
    else{ exgcd(b, a % b, d, y, x); y -= x * (a / b);}
}

//n个方程:x=a[i](mod m[i])
ll china(int n, int* a, int* m)
{
    ll M = 1, d, y, x = 0;
    for (int i = 0; i < n; i++)  M *= m[i];
    for (int i = 0; i < n; i++)
    {
        ll w = M / m[i];
        exgcd(m[i], w, d, d, y);        //d共用了
        x = (x + y * w * a[i]) % M;   //x相当于sum
    }
    return (x + M) % M;
}

int n;
int res[5];

int main()
{
    init();

    int T;
    scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &n);
        for(int i = 0;i < 5;i++)  res[i] = solve(n, m[i], i);

//        for(int i = 0;i < 5;i++)  printf("%d ", res[i]);
//        printf("\n");

        int ans = china(5, res, m);
        printf("%d\n", ans);
    }
    return 0;
}

参考链接:

1. https://zh.wikipedia.org/w/index.php?title=%E8%B4%9D%E5%B0%94%E6%95%B0

2. https://www.cnblogs.com/yuyixingkong/p/4489189.html

3.https://www.cnblogs.com/Chierush/p/3344661.html

原文地址:https://www.cnblogs.com/lfri/p/11546313.html

时间: 2024-11-13 08:14:43

hdu477 Bell——求第n项贝尔数的相关文章

贝尔数(来自维基百科)&amp; Stirling数

贝尔数 贝尔数以埃里克·坦普尔·贝尔(Eric Temple Bell)为名,是组合数学中的一组整数数列,开首是(OEIS的A000110数列): Bell Number Bn是基数为n的集合的划分方法的数目.集合S的一个划分是定义为S的两两不相交的非空子集的族,它们的并是S.例如B3 = 5因为3个元素的集合{a, b, c}有5种不同的划分方法: {{a}, {b}, {c}} {{a}, {b, c}} {{b}, {a, c}} {{c}, {a, b}} {{a, b, c}}; B0

hdu 2521 一卡通大冒险 (斯特灵数,贝尔数)

/* 将N张卡分成若干个集合,集合不为空,有多少种分法. f[n][m]表示n张卡分成m组的种类数,那么f[n][m]=f[n-1][m-1]+f[n-1][m]*m,//第二类斯特灵数 而ans[n]=sum{f[n][l]}(1<=l<=m).//ans为贝尔数,Bell数是将P个元素集合分到非空且不可区分例子的划分个数. 其中:f[n-1][m-1]代表第n个人自成一堆: f[n-1][m]*m代表第n个人不自成一堆. */ # include <stdio.h> # inc

HDU2512 一卡通大冒险【斯特灵数,贝尔数】

题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=2512 题目大意: 有N张卡,将N张卡分成若干不同的集合,集合不能为空.问:总共有多少种分法. 思路: 参考博文:http://blog.csdn.net/acm_cxlove/article/details/7857671 集合的个数可以为1.2.3.-.N.问题就变为了把N张卡放到i个集合中. 这时候个组合问题,可以用第二类斯特灵数解决. S(P,K) = S(P-1,K-1) + K*S(P-

挑战程序设计竞赛 划分数,贝尔数,斯特灵数

斯特灵数:把n个数划分为恰好k个非空集合的个数,记为S(n,k).且有:S(n,1)=S(n,n)=1. 有递推关系式: S(n+1,k)=S(n,k?1)+kS(n,k?1) 贝儿数:把n个数划分为非空集合的所有划分数.有: Bn=∑i=0nS(n,i) 贝尔数的递推公式: Bn=∑k=0n(nk)Bk 书上的划分数:书上求的是:把n个相同的数划分为不超过m个集合的方法总数.由于这n个数是相同的,就不能算作∑ki=0S(n,i).书上给了这样一个dp的转移方程(定义dp[i][j]为j个数的i

HDU 1568 Fibonacci【求斐波那契数的前4位/递推式】

Fibonacci Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Problem Description 2007年到来了.经过2006年一年的修炼,数学神童zouyu终于把0到100000000的Fibonacci数列 (f[0]=0,f[1]=1;f[i] = f[i-1]+f[i-2](i>=2))的值全部给背了下来. 接下来,CodeStar决定要考考他,于是每问他一

bzoj 3501 PA2008 Cliquers Strike Back——贝尔数

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3501 用贝尔三角形 p^2 地预处理 p 以内的贝尔数.可以模(mod-1)(它是每个分解下的质因子的倍数,所以不影响分开算的时候). 用公式:\( Bell[n+p^{m}]=m*Bell[n]+Bell[n+1] (mod p) \) \( Bell[n+p]=Bell[n]+Bell[n+1] (mod p) \) 把 n 看成 p 进制,O( p^2 * log m ) 地算. 大

《数据结构与算法分析:C语言描述》读书笔记------练习1.1 求第K大的数

求一组N个数中的第k个最大者,设k=N/2. 1 import java.util.Random; 2 3 4 public class K_Max { 5 6 /** 7 * @param args 8 */ 9 //求第K大的数,保证K大于等于1,小于等于array.length/2哦 10 public static int TopK(int array[],int K) 11 { 12 int topk[] = new int [K]; 13 for(int i = 0; i<topk.

Quick-Select 1亿个数快速求第K小的数 分治法

Quick-Select  1亿个数快速求第K小的数  分治法 利用快速排序的思想,一开始选取中枢元,然后左右调整,接着比对中枢元p和K的大小,如果 p+1 = k (数组从0开始), 那么a[p] 就是答案,因为在p之前的,肯定都是小于a[p]的, 在p之后的,肯定大于p, 所以 a[p] 就是第 p+1 小.假如 p+1 不等于K, 那么根据大小,进行左右调整.调整过程中,理想状态下,每次都砍掉一半,数组的起始坐标要进行调整. 代码: // 快速排序法的修改 #include <iostre

HDU 2512 贝尔数

一卡通大冒险 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 1356    Accepted Submission(s): 890 Problem Description 因为长期钻研算法, 无暇顾及个人问题,BUAA ACM/ICPC 训练小组的帅哥们大部分都是单身.某天,他们在机房商量一个绝妙的计划"一卡通大冒险".这个计