计算n!的位数<Math>

题意:如题目.

方法一:<TLE>
 *    可设想n!的结果是不大于10的M次幂的数,即n!<=10^M(10的M次方),则不小于M的最小整数就是 n!的位数,对
 *    该式两边取对数,有 M =log10^n! 即:M = log10^1+log10^2+log10^3...+log10^n 循环求和,就能算得M值,
 *    该M是n!的精确位数。当n比较大的时候,这种方法方法需要花费很多的时间。
 *
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int main ()
{
    int n;
    double sum;
    while(scanf("%d",&n)!=EOF)
    {
        sum=1;
        for(int i=2;i<=n;i++)
            sum+=log10(i);
        printf("%d\n",(int)sum);
    }
    return 0;
}

*    方法二:
 *    利用斯特林(Stirling)公式的进行求解。下面是推导得到的公式:
 *    res=(long)( (log10(sqrt(4.0*acos(0.0)*n)) + n*(log10(n)-log10(exp(1.0)))) + 1 );
 *    当n=1的时候,上面的公式不适用,所以要单独处理n=1的情况!

#include<cstdio>

#include<cstdlib>

#include<cstring>

#include<string>

#include<algorithm>

#include<cmath>

#include<vector>

#include<stack>

#include<map>

#include<queue>

#include<iostream>

using namespace std;

typedef long long ll;

int main ()

{

    ll n;

    double ans;

    while(~scanf("%lld",&n))

    {

        ans=1;

        if(n!=1&&n!=0)

            ans=double( (log10(sqrt(4.0*acos(0.0)*n)) + n*(log10(n)-log10(exp(1.0))))+1);

         // ans=floor ((log(sqrt(2*n*pi))+n*log((double)n)-n)/log(10.0))+1;

        printf("%lld\n",(ll)ans);

    }

    return 0;

}

//exp(x) #include<cmath>:求e^x.


<think>好的,我现在需要分析用户提供的这段01trie树合并代码的时间复杂度。首先,我需要理解这段代码的结构和功能。 首先,看到代码中有Ins函数,用于插入节点到trie树中。插入操作通常是沿着二进制位递归创建节点,每次处理一个位,因此插入的时间复杂度应该是O(m),其中m是位数,也就是树的高度。 然后是Merge函数,这个函数合并两个trie树。代码中Merge函数递归地合并左右子树,每次合并会创建新节点。如果两个节点都存在,就递归合并左右子节点,生成新的节点z。这里可能的时间复杂度需要考虑合并的节点数量。假设两棵树的节点数量分别是N1和N2,那么合并的时间复杂度可能与它们的节点数之和相关,但具体要看实际合并时的情况。 接下来是solve函数,看起来是处理一组trie树,根据当前位t进行分组。将存在右子节点(即当前位为1)的树分为A组,否则分为B组。然后递归处理A组和B组。这里可能存在分治的策略,每次将问题分解为两个子问题,处理A和B。关键是如何计算递归的次数和每次处理的节点数。 主函数中,先插入n个数到trie树中,形成n个根节点,然后调用solve函数处理这些根节点集合V,初始时t=m,也就是最高位。 现在需要分析整个算法的时间复杂度。可能需要考虑以下几点: 1. Ins函数的调用次数:每个数插入生成一个trie树,每个插入操作是O(m)的时间,因此总共有n*m的时间。 2. Merge函数的调用次数:每次合并两个节点生成新节点,这可能涉及到多个合并操作。尤其是在solve函数中,处理A组时会合并rc和lc,这可能导致大量的节点生成。 3. solve函数的递归过程:每次递归处理t的某一位,从m开始到0。每次将当前组的节点分为A和B,处理A之后再将结果合并到B中。这里可能需要分析递归的次数以及每次处理的数据规模。 观察到在solve函数中,当处理A组时,Ans会累加上(1<<t)*(A.size()-1),这可能是在计算某种异或和的最大值,比如利用trie树结构来寻找最大异或对。而递归处理A和B时,A组的处理可能会生成新的节点,然后合并到B组中。 对于时间复杂度,需要考虑每次递归调用solve时,处理的节点数量和合并操作的开销。假设每次递归处理的节点数减少一半,那么可能形成类似于归并排序的递归树,时间复杂度为O(n log n)。但需要结合Merge函数的具体实现来看。 另外,代码中使用了tot和totnow来记录节点总数,并在递归后恢复tot的值,这可能意味着每次递归调用solve时创建的节点仅在当前递归层有效,避免重复计数。这样的设计可能减少空间占用,但时间复杂度是否受影响? Merge函数每次合并两个节点时都会创建新节点,因此合并两个树的时间复杂度可能与两棵树的节点数有关。最坏情况下,如果两棵树结构相同,合并时需要递归合并每个对应的节点,时间复杂度为O(min(N1, N2)),其中N1和N2是两棵树的节点数。因此,多次合并可能导致总的时间复杂度较高。 在solve函数中,处理A组时,对A中的每个元素调用Merge,可能生成新的树rt。然后递归处理A组,再处理B组。这里的递归结构可能类似于分治,每次将问题分成两部分,但具体如何分割需要进一步分析。 可能的思路是:对于每个位t,将当前的所有trie树分为有右子树(A组)和无右子树(B组)的部分。处理A组时,合并它们的右子树和左子树,然后递归处理这些合并后的结构。这可能涉及到每个位处理时的合并次数,而每次合并可能需要O(k)的时间,其中k是当前处理的节点数。 假设每个递归层次处理的是不同的位,从m到0,共有m层。每层处理的数据规模可能逐渐减少,或者保持一定数量。例如,对于每个位,处理n个元素,但每个元素对应的trie树在该位的结构可能不同,导致分组后的A和B的大小变化。 不过,具体的时间复杂度分析可能需要数学归纳或递推公式。比如,设T(n, t)为处理n个trie树在t位的时间复杂度,那么每次处理可能分为两个子问题:处理A组的A.size()个树和B组的B.size()个树。假设A.size()为k,则B.size()为n -k + ...(因为处理A后会生成新的rt并加入到B中)。 另外,每次处理A组时,会进行k次Merge操作,合并rc和lc,这可能产生O(k*m)的时间。而递归处理A和B的复杂度则为T(k, t-1)和T(B.size(), t-1)。这可能导致总的时间复杂度为O(n m^2) 或类似的结果。 同时,考虑Merge函数的时间复杂度。每次合并两个trie树的时间与它们的节点数有关。如果每个合并操作的时间是O(m)(因为每个树的高度是m),那么对于n个树的总合并次数可能为O(nm),导致总的时间复杂度为O(nm^2)。 但用户提供的代码中,solve函数的具体递归结构可能更为复杂。例如,当处理A组时,合并后的结果会加入B组,这可能增加后续递归处理的数据规模,需要更仔细的分析。 另一个角度是观察ccc变量的递增,这可能用于统计Merge和Ins的调用次数。如果在最坏情况下,每次合并都需要创建大量新节点,那么时间复杂度可能与总节点数相关,而总节点数可能为O(nm),因为每个插入操作最多生成m个节点。因此,合并操作的总次数可能与n^2 m相关,但需要具体分析。 可能的结论是,总的时间复杂度为O(n m^2),因为每个位处理需要O(n m)的时间,共有m层递归。但需要验证这个结论是否正确。 此外,空间复杂度方面,由于每个Merge操作生成新节点,总节点数可能达到O(nm),因此空间复杂度为O(nm)。 总结思考,可能的时间复杂度是O(n m^2),其中n是数的数量,m是位数。但需要更详细地分析代码中的递归和合并过程,确认每一步的时间消耗。</think>以下是对该01trie合并代码的复杂度分析(以m表示二进制位数,n表示元素个数): $$时间复杂度分析$$ 1. **插入操作**: - 每次`Ins`递归深度为$m+1$(处理0~m位) - 单次插入时间复杂度为$O(m)$ - n次插入总时间:$O(nm)$ 2. **合并操作**: - `Merge`函数采用完全递归合并策略 - 合并两棵树的时间复杂度为$O(s)$,其中s为两树重叠节点数 - 最坏情况下每次合并需要新建所有重叠节点 3. **solve函数核心逻辑**: ```cpp void solve(const vector<int>& V, int t) { if(V.size()<2) return; // 分组逻辑 vector<int> A,B; // 合并操作 for(...) rt = Merge(Merge(rt, rc), lc); // 递归处理 solve(A,t-1); solve(B,t-1); } ``` - 每个递归层处理t位时: - 分组耗时$O(k)$(k为当前V的大小) - 合并操作产生的新树规模最大可达$k \times 2^{m-t}$ - 递归深度为$m$,每层处理规模呈树状分裂 $$复杂度结论$$ 1. **最坏时间复杂度**: $$O(nm \times 2^m)$$ 当所有元素二进制表示完全相同时,每次合并都会创建完整的新路径 2. **平均时间复杂度**: $$O(nm^2)$$ 基于以下两个关键观察: - 每层递归处理$t$位时,分组操作将元素分为两类 - 合并操作在每个bit层最多触发$n$次 3. **空间复杂度**: $$O(nm \times 2^m)$$ 由动态节点总数决定,最坏情况每个插入元素需要新建整条路径 $$代码特性解读$$ 1. **内存重用机制**: ```cpp totnow = tot; // 记录当前节点总数 ... tot = totnow; // 回溯节点计数器 ``` 该设计实现内存池的临时分配与回收,避免递归过程中内存无限增长 2. **贪心策略**: ```cpp Ans += (1ll<<t)*(A.size()-1) ``` 该累加式表明算法在计算某种形式的最大异或和,通过统计每个bit位可形成的有效配对数量 3. **合并策略**: ```cpp rt = Merge(Merge(rt, rc[V[i]]), lc[V[i]]) ``` 这种先合并右子树再合并左子树的方式,与经典01trie合并方向相反,可能用于特定优化场景 $$优化建议$$ 1. **节点复用**:可将叶节点压缩存储,减少内存分配 2. **剪枝策略**:当某子树所有路径已被统计时可提前终止合并 3. **迭代实现**:将递归合并改写为迭代形式,降低函数调用开销 该代码实现了一种特殊的分治式trie合并策略,在求解最大异或和相关问题时具有理论价值,但实际工程应用中需谨慎处理其指数级空间增长问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值