3348 可构造的序列总数

dp数组含义:

dp[i][j]长度为i末尾元素为j的数组

状态转移方程

初始化

//本题的难点仍在于dp数组含义的设计 和 状态转移方程的列写

//对于序列中的每个元素ai,其值只与前一个元素有关,与前面其他元素无关
//且前一个元素与后一个元素成倍数关系,且不超过K

//故令dp[i][j]表示序列长度为i,且以j结尾的合法序列的数量
//设j的因子列表为ki,则dp[i][j]需要不断累加所有的dp[i-1][ki]
//如当前j=8,其因子有1,2,4,8
//则dp[i][8]=dp[i-1][1]+dp[i-1][2]+dp[i-1][4]+dp[i-1][8]
//若想当长度为i的时候末尾是8,则长度为i-1的时候末尾只能是1,2,4,8
//即长为i,以8结尾的序列数量等于长为i-1,以1,2,4,8结尾的序列的数量之和
//可以理解为对于长为i-1的序列,只有末尾为1,2,4,8时,再添加一个元素才有可能是8

//此外还要注意两个问题:
//一是初始化,当长度为1时不论选择1~K中的何值,都只有一种方案,故dp[1][1]~dp[1][K]均初始化为1 
//二是在求j的因子时,为了提高效率只遍历到根号j,需要注意ki恰好为j的平方根时,不能重复累加 
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const ll mod=1e9+7;

ll dp[2005][2005];//dp[i][j]表示序列长度为i,且以j结尾的合法序列的数量 

int main()
{
    int N,K;
    ll ans=0;
    cin>>K>>N;
    for(int i=1;i<=K;i++)dp[1][i]=1;//只有1个数,无论是选择1~K中哪个数,都只有1种方案 
    
    for(int i=2;i<=N;i++)//从2~N遍历每一种序列长度 
    { 
        for(int j=1;j<=K;j++)//遍历当前序列中最后一个元素的每一种可能的取值j 
        {
            for(int k=1;k*k<=j;k++)//遍历每一个可能是j的因子的数 
            //1  2  3  4  |  6  
            //36 18 12 9  |  6
            {
                if(k*k==j)//若k恰好是j的平方根,j/k==k,避免重复累加dp[i-1][k],与下文相区分 
                {
                    //长为i且以j结尾的序列个数 累加上 长为i-1且以k结尾的序列个数 
                    dp[i][j]=(dp[i][j]+dp[i-1][k])%mod;
                } 
                else if(j%k==0)//否则如果k是j的因子且不是平方根,一次累加一对因子,效率更高 
                {
                    //长为i且以j结尾的序列个数 累加上 长为i-1且以j/k结尾的序列个数和长为i-1且以k结尾的序列个数 
                    dp[i][j]=(dp[i][j]+dp[i-1][k]+dp[i-1][j/k])%mod;
                } 
            }    
        } 
    }    
    
    for(int i=1;i<=K;i++)//累加所有长度为N的以1~K结尾的序列数量 
    {
        ans=(ans+dp[N][i])%mod;
    }
    cout<<ans<<endl;
    return 0;
}

### 构造二叉树的方法 通过前序和中序遍历序列构造二叉树的核心在于理解两种遍历方式的特点以及它们之间的关系。以下是详细的说明: #### 方法概述 前序遍历的第一个节点总是当前子树的根节点[^2]。而在中序遍历中,根节点会将整个数组分为两部分:左侧为左子树的所有节点,右侧为右子树的所有节点。因此可以通过以下方法逐步构建二叉树。 1. **确定根节点** 前序遍历中的第一个元素即为整棵树的根节点。找到这个根节点在中序遍历中的位置,则可以划分出左子树和右子树对应的范围。 2. **递归构建左右子树** 利用分治的思想,在中序遍历中分别找出左子树和右子树的部分,并继续调用相同的逻辑来处理这些部分,直至所有节点都被正确放置到其对应的位置。 3. **终止条件** 当某个区间为空时(即没有更多节点),停止递归并返回 `None` 表示此分支结束。 --- ### 实现代码 下面是一个基于 Python 的实现例子,展示了如何根据给定的前序和中序遍历来重建二叉树: ```python class TreeNode: def __init__(self, val=0, left=None, right=None): self.val = val self.left = left self.right = right def buildTree(preorder, inorder): if not preorder or not inorder: return None root_val = preorder[0] # 根节点值来自前序遍历的第一个元素 root_index_in_inorder = inorder.index(root_val) # 找到根节点在中序遍历中的索引 # 创建根节点 root = TreeNode(root_val) # 左子树的长度等于根节点在中序遍历中的索引 left_length = root_index_in_inorder # 使用切片操作获取左子树和右子树的前序、中序遍历数据 root.left = buildTree(preorder[1:1 + left_length], inorder[:left_length]) root.right = buildTree(preorder[1 + left_length:], inorder[root_index_in_inorder + 1:]) return root ``` --- ### 示例运行过程 假设输入如下: - 前序遍历:`preorder = [1, 2, 4, 7, 3, 5, 6, 8]` - 中序遍历:`inorder = [4, 7, 2, 1, 5, 3, 8, 6]` 执行流程如下: 1. 根据前序遍历 `[1,...]` 可知根节点为 `1`。 2. 在中序遍历 `[...,1,...]` 中定位到 `1` 的下标为 `3`,从而划分子树: - 左子树中序遍历为 `[4, 7, 2]`; - 右子树中序遍历为 `[5, 3, 8, 6]`。 3. 对应于前序遍历: - 左子树前序遍历为 `[2, 4, 7]`; - 右子树前序遍历为 `[3, 5, 6, 8]`。 4. 继续递归地应用相同的过程,最终得到完整的二叉树结构。 --- ### 复杂度分析 - 时间复杂度:O(n),其中 n 是节点总数。每次查找根节点都需要 O(1) 或者优化后的哈希表辅助达到平均时间复杂度降低的效果。 - 空间复杂度:O(h),h 代表树的高度,主要取决于递归栈的空间消耗。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值