zoukankan      html  css  js  c++  java
  • hdu 3240

    博客图片

    题目链接

    hdu3240

    题目概述

            给出(n)个结点,最多(n)个结点可以组成的不同形态的二叉树的数目.结果很大,所以输出的结果要对(m)取模,其中((1 leq n leq 100000), (1 leq m leq 100000000)).

    一点思路

            因为是最多用(n)个结点可以构成的总数,实际是1个结点,2个结点,(dots,n)个结点可以构成的不同形态的二叉树的数目之和.所以问题就转化成求(n)个相同的结点可以构成的不同的二叉树形态的数目.假设(n)个结点可以构成的二叉树的(f(n))个.对于(n)个相同的结点,首先有一个结点是根节点,然后对于剩下的(n-1)个结点来说,讲它们分成两部分,分别构成左右子树,可以分成((0,n-1),(1,n-2),dots(n-1,0))中,分别表示,左子树由(0)个结点构成右子树由((n-1))个结点构成,左子树由(1)个结点构成右子树由((n-2))个结点构成,(dots),左子树由(n-1)个结点构成右子树由(0)个结点构成,由加法原理和乘法原理:

    [egin{aligned} f(n) &= f(0)f(n-1)+f(1)f(n-2)+cdots+f(n-1)f(0) \ &= sum_{i=0}^{n-1}{f(i)f(n-1-i)},qquad f(0) = 1 end{aligned} ]

    这个恰好就是(Catalan)数的一个计算表示的方法.所以,(n)个相同结点可以构成的不同形态的二叉树的数目恰好是(C_n).

            然后是解决计算(Catalan)数取模的问题.(C_n)的计算有三个公式:

    [egin{aligned} C_n &= C_0C_{n-1}+C_1C_{n-2}+cdots+C_{n-1}C_0 = sum_{i=0}^{n-1}C_iC_{n-1-i}, qquad C_0 = 1\ C_n &= frac{4*n-2}{n+1}C_{n-1}, qquad C_0=1\ C_0 &= frac{1}{n+1}C_{2n}^n=frac{(2n)!}{(n+1)!n!} end{aligned} ]

            最初我用的是第一个式子计算提交的,然后TLE.然后第二个因为有一个分式,并且不能总保证((4*n-2)\%(n+1)=0)总成立,而(C_{n-1})在前面的计算中已经取模了,很明显如果直接分子分母取模再相除结果一定是不对的,所以我最初的想法是通过通过逆元把(frac{4*n-2}{n+1}\%m)转成(((4*n-2)k)\%m)来进行计算,其中的(k)((n+1))(m)的逆,但是发现这个会出问题,因为((n+1)kequiv 1\, (mod m)),只有当(m)是素数的条件下才成立,比如当(n=1,m=100)时,显然是没法求出这个逆元的.然后就一直卡在这里了.┭┮﹏┭┮

    进一步的想法

            对于模数(m),来说,满足(m=p_1^{a_1}p_2^{a_2}cdots p_k^{a_k},\, p_i)是素数(,a_ige1.)所以可以先从((4*n-2))((n+1))中先剔除掉(m)的素因子,如果此时的((n+1) !=1)那么可以通过求逆的方法,把原来的除法取模变成乘法逆元取模了.然后这个然后再乘上哪些提出掉的素因子再对(m)取模得到的就是(C_n\%m).

    代码实现

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N = 1e5+5;
    // ll S[N];
    
    // 扩展gcd
    void extend_gcd(ll a, ll b, ll& x, ll& y) {
        if(b == 0){
            x = 1;
            y = 0;
            return;
        }
        extend_gcd(b, a%b, x, y);
        ll temp = x;
        x = y;
        y = temp - (a / b) * y;
    }
    
    // 计算a模m的逆
    ll mod_inverse(ll a, ll m){
        ll x, y;
        extend_gcd(a, m, x,y);
        return (m + x % m) % m;
    }
    
    // // 前项和乘积类加求解,TLE
    // void calculate(int n, int m){
    //     memset(S, 0, sizeof(S));
    //     S[0] = 1;
    //     ll ans = 0;
    //     for (int i = 1; i <= n; ++i){
    //         for (int j = 0; j < i; ++j){
    //             S[i] += S[j]*S[i-1-j];
    //             S[i] %= m;
    //         }
    //         ans += S[i];
    //         ans %= m;
    //     }
    //     printf("%lld
    ", ans);
    // }
    
    // 利用递推式和逆元取模计算
    void calculate(int n, int m){
        ll primes[N] = {0};
        int cnt = 0;
        ll nums[N] = {0};
        // 计算m的素因子
        ll temp = m;
        for (ll i = 2; i * i <= temp;++i){
            if(temp% i == 0){
                primes[cnt++] = i;
                // 剔除调m中的所有素因子i
                while(temp % i == 0){
                    temp /= i;
                }
            }
        }
        // 判断最后剩下m是不是一个素数
        if( temp != 1)
            primes[cnt++] = temp;
        ll pre = 1;
        ll ans = 0;
        ll cur = 1;
        for (int i = 1; i <= n; ++i){
            //去掉(4*i-2)中m的素因子,记录这些素因子的个数
            ll k = 4 * i - 2;
            for (int j = 0; j < cnt; j++){
                // (4*i-2)中包含了m的某个素因子不止一次.
                while( k % primes[j] == 0){
                    ++nums[j];
                    k /= primes[j];
                }
            }
            pre *= k;
            pre %= m;
            // 去掉(i+1)中m的素因子,把这些素因子的个数从前面(4*i-2)中剔除
            k = i + 1;
            for (int j = 0; j < cnt; j++){
                // (4*i-2)中包含了m的某个素因子不止一次.
                while( k % primes[j] == 0){
                    --nums[j];
                    k /= primes[j];
                }
            }
            // 计算剔除素因子后(i+1)的逆元
            if( k != 1)
                k = mod_inverse(k, m);
            pre *= k;
            pre %= m;
            cur = pre;
            for (int j = 0; j < cnt; j++){
                // 乘以之前剔除的素因子
                for (int k = 0; k < nums[j]; ++k)
                    cur = (cur * primes[j]) % m;
            }
            ans += cur;
            ans %= m;
        }
        printf("%lld
    ", ans);
    }
    
    int main(int argc, const char** argv) {
        int n = 0, m = 0;
        while(scanf("%d%d", &n,&m) && (n+m != 0)) {
            calculate(n,m);
        }
        return 0;
    }
    

            这里面我觉得可能用一忽略的一个坑点就是那在求(m)的素因子,不仅仅求的是这一个素因子(p_i),还要把(m)中的(p_i^{a_i})剔除掉,所以要不断的除以这个素因子(p_i),直到不再包含.然后要分清楚这里的cur也就是乘以当前剔除掉的素因子(分子分母相同的素因子会剔除掉一部分).

    补充

  • 相关阅读:
    Spring笔记②--各种属性注入
    Spring笔记①--helloworld
    Structs2笔记③--局部类型转换案例
    Struct2笔记②--完善登陆代码
    Structs2笔记①--structs的背景、structs2框架的意义、第一个helloworld
    软件项目的开发之svn的使用
    Java基础第一节.Java简介
    Hibernate笔记④--一级二级缓存、N+1问题、saveorupdate、实例代码
    Hibernate笔记③--集合映射、组合映射、联合主键、查询案例
    Hibernate笔记②--hibernate类生成表、id生成策略、级联设置、继承映射
  • 原文地址:https://www.cnblogs.com/2018slgys/p/13292887.html
Copyright © 2011-2022 走看看