递推公式黑科技

本文介绍了一种使用递推公式和线性序列解决特定数学问题的方法,通过高效的算法实现快速计算。文中详细展示了如何利用多项式乘法和模运算进行线性递推,并通过伯恩斯坦算法优化计算过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

//递推公式黑科技
#include<bits/stdc++.h>
using namespace std;
#define X first
#define Y second
#define PB push_back
#define MP make_pair
#define MEM(x,y) memset(x,y,sizeof(x));
#define bug(x) cout<<"bug"<<x<<endl;
typedef long long ll;
typedef pair<int,int> pii;
using namespace std;
const int maxn=1e3+10;
const int mod=998244353;
ll powmod(ll a,ll b){
    ll res=1;a%=mod;
    assert(b>=0);
    for(;b;b>>=1){
        if(b&1)res=res*a%mod;a=a*a%mod;
    }
    return res;
}
// head
namespace linear_seq {
    const int N=10010;
    ll res[N],base[N],_c[N],_md[N];
    vector<int> Md;
    void mul(ll *a,ll *b,int k) {
        for(int i=0;i<k+k;i++) _c[i]=0;
        for(int i=0;i<k;i++)
            if (a[i])
                for(int j=0;j<k;j++) _c[i+j]=(_c[i+j]+a[i]*b[j])%mod;
        for (int i=k+k-1;i>=k;i--)
            if (_c[i])
                for(int j=0;j<Md.size();j++)
                    _c[i-k+Md[j]]=(_c[i-k+Md[j]]-_c[i]*_md[Md[j]])%mod;
        for(int i=0;i<k;i++) a[i]=_c[i];
    }
    int solve(ll n,vector<int> a,vector<int> b) {
    // a 系数 b 初值 b[n+1]=a[0]*b[n]+...
        ll ans=0,pnt=0;
        int k=a.size();
        assert(a.size()==b.size());
        for(int i=0;i<k;i++) _md[k-1-i]=-a[i];_md[k]=1;
        Md.clear();
        for(int i=0;i<k;i++) if (_md[i]!=0) Md.push_back(i);
        for(int i=0;i<k;i++) res[i]=base[i]=0;
        res[0]=1;
        while ((1ll<<pnt)<=n) pnt++;
        for (int p=pnt;p>=0;p--) {
            mul(res,res,k);
            if ((n>>p)&1) {
                for (int i=k-1;i>=0;i--) res[i+1]=res[i];res[0]=0;
                for(int j=0;j<Md.size();j++) res[Md[j]]=(res[Md[j]]-res[k]*_md[Md[j]])%mod;
            }
        }
        for(int i=0;i<k;i++) ans=(ans+res[i]*b[i])%mod;
        if (ans<0) ans+=mod;
        return ans;
    }
    vector<int> BM(vector<int> s) {
        vector<int> C(1,1),B(1,1);
        int L=0,m=1,b=1;
        for(int n=0;n<s.size();n++) {
            ll d=0;
            for(int i=0;i<L+1;i++) d=(d+(ll)C[i]*s[n-i])%mod;
            if (d==0) ++m;
            else if (2*L<=n) {
                vector<int> T=C;
                ll c=mod-d*powmod(b,mod-2)%mod;
                while (C.size()<B.size()+m) C.PB(0);
                for(int i=0;i<B.size();i++) C[i+m]=(C[i+m]+c*B[i])%mod;
                L=n+1-L; B=T; b=d; m=1;
            } else {
                ll c=mod-d*powmod(b,mod-2)%mod;
                while (C.size()<B.size()+m) C.PB(0);
                for(int i=0;i<B.size();i++) C[i+m]=(C[i+m]+c*B[i])%mod;
                ++m;
            }
        }
        return C;
    }
    int gao(vector<int> a,ll n) {
        vector<int> c=BM(a);
        c.erase(c.begin());
        for(int i=0;i<c.size();i++) c[i]=(mod-c[i])%mod;
        return solve(n,c,vector<int>(a.begin(),a.begin()+c.size()));
    }
};

int main(){
    ll t,n;
//    cin>>t;
    while(cin>>n){
        cout<<(linear_seq::gao(vector<int>{5,13,34,89},n-1)%mod-1)%mod<<endl;
    }
}

<think>首先,用户的问题是关于“递推公式的定义和应用示例”。递推公式在数学和计算机科学中很常见,特别是在算法和动态规划中。 回顾系统指令: - 所有行内数学表达式用 `$...$` 格式。 - 独立公式用 `$$...$$` 格式并单独成段。 - 使用中文回答。 - 生成相关问题。 - 在回答中引用的段落末尾自然地添加引用标识,如 `[^1]`。 - 回答结构清晰,帮助用户逐步解决问题。 用户提供了几个引用,我需要参考这些引用: - 引用[1]:讨论了0-1背包问题的递推公式,如 `dp[j] = max(dp[j], dp[j - nums[i]] + nums[i])`,用于分割等和子集问题。 - 引用[2]:展示了0-1背包问题的代码实现和推导。 - 引用[3]:涉及卡特兰数公式,如 `h(n) = c(2n,n) - h(2n,n-1)`,并给出了代码。 - 引用[4]:讨论了最长公共子序列的递推公式,如 `dp[i][j] = dp[i-1][j-1] + 1` 如果字符相等,否则 `max(dp[i-1][j], dp[i][j-1])`。 用户意图是寻找递推公式的定义和应用示例,可能涉及算法或数学领域。因此,我的回答应该包括: 1. **定义**:解释什么是递推公式。 2. **应用示例**:提供一些常见的例子,如斐波那契数列、背包问题、卡特兰数等。 3. **引用相关部分**:自然地引用提供的引用内容。 4. **生成相关问题**:在末尾添加相关问题。 结构回答: - **引言**:简要介绍递推公式。 - **定义部分**:清晰定义递推公式。 - **应用示例部分**:提供几个例子,包括数学和算法示例。 - **结论**:总结递推公式的重要性。 - **相关问题**:生成2-3个相关问题。 确保使用正确的LaTeX格式: - 行内数学表达式用 `$...$`,如 `$a_n = a_{n-1} + d$`。 - 独立公式用 `$$...$$`,如斐波那契数列的公式。 参考用户提供的引用: - 在示例中,使用引用[1]中的背包问题例子。 - 使用引用[3]中的卡特兰数例子。 - 使用引用[4]中的最长公共子序列例子。 最终,回答以中文呈现。</think>### 递推公式的定义与应用示例 递推公式(Recurrence Relation)是数学和计算机科学中一种常见的表达方式,它通过一个或多个初始值和递归关系来定义序列中的后续项。简单来说,递推公式描述了如何从一个或多个已知项推导出序列中的下一个项。这种公式广泛应用于算法设计(如动态规划)、组合数学、数值分析等领域。下面我将逐步解释其定义,并提供多个应用示例,确保内容真实可靠,并基于算法和数学的常见知识。 #### 1. 递推公式的定义 递推公式的核心思想是:**通过序列的前一项或前几项,推导出当前项**。形式上,一个递推公式通常表示为: $$ a_n = f(a_{n-1}, a_{n-2}, \dots, a_{n-k}) + c $$ 其中: - $a_n$ 是序列的第 $n$ 项。 - $f$ 是一个函数,定义了如何从前 $k$ 项计算 $a_n$。 - $c$ 是常数或额外项(可能为0)。 - 初始条件(如 $a_0$ 和 $a_1$)必须给定,以确保序列有唯一解。 递推公式的关键特点包括: - **递归性**:每一项依赖于前面的项。 - **初始值**:序列的起始点必须明确指定。 - **应用范围**:常用于描述离散序列(如斐波那契数列)或优化问题(如动态规划中的状态转移)。 在算法中,递推公式常用于动态规划(DP),通过存储子问题的解来避免重复计算,提高效率[^1]。例如,在0-1背包问题中,递推公式定义了状态转移逻辑。 #### 2. 应用示例 递推公式在数学和算法中应用广泛。以下是几个经典示例,涵盖简单数学序列和复杂算法问题。每个示例包括公式描述、应用场景和简要分析。 **示例1: 斐波那契数列(数学基础示例)** 斐波那契数列是递推公式的经典例子,用于描述生长模型或金融计算。 - **递推公式**: $$ F_n = F_{n-1} + F_{n-2} \quad \text{其中} \quad n \geq 2, \quad F_0 = 0, \quad F_1 = 1 $$ - **应用场景**:计算斐波那契数(如 $F_5 = 5$)。 - **代码实现(Python)**: ```python def fibonacci(n): if n <= 1: return n a, b = 0, 1 for _ in range(2, n+1): a, b = b, a + b # 递推步骤:基于前两项计算下一项 return b print(fibonacci(5)) # 输出:5 ``` - **分析**:该公式简单高效,时间复杂度为 $O(n)$。初始值 $F_0$ 和 $F_1$ 确保序列唯一性。 **示例2: 0-1背包问题(算法优化示例)** 在动态规划中,递推公式用于解决资源分配问题,如背包问题。参考引用[1],该问题要求从物品中选择子集,使总重量不超过背包容量且价值最大。 - **递推公式**: $$ \text{dp}[j] = \max(\text{dp}[j], \text{dp}[j - w_i] + v_i) $$ 其中: - $\text{dp}[j]$ 表示背包容量为 $j$ 时的最大价值。 - $w_i$ 和 $v_i$ 是第 $i$ 个物品的重量和价值。 - 初始条件:$\text{dp}[0] = 0$(空背包的价值为0)。 - **应用场景**:分割等和子集问题(LeetCode 416),判断数组是否能被分成两个和相等的子集[^1]。引用[1]中的代码展示了如何用此公式求解: ```cpp bool canPartition(vector<int>& nums) { int sum = 0; for (int n : nums) sum += n; if (sum % 2 == 1) return false; sum /= 2; vector<int> dp(sum + 1, 0); for (int j = 0; j < sum + 1; ++j) if (j >= nums[0]) dp[j] = nums[0]; for (int i = 1; i < nums.size(); ++i) { for (int j = sum; j >= nums[i]; --j) { dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]); // 递推公式应用 } } return dp[sum] == sum; } ``` - **分析**:该公式通过滚动数组优化空间,时间复杂度 $O(n \times \text{capacity})$。它体现了动态规划的核心:基于子问题解构建全局解[^2]。 **示例3: 卡特兰数(组合数学示例)** 卡特兰数用于计数问题,如括号匹配或二叉树结构。参考引用[3],其递推公式基于组合数。 - **递推公式**: $$ C_n = \frac{1}{n+1} \binom{2n}{n} \quad \text{或} \quad C_n = \sum_{i=0}^{n-1} C_i C_{n-1-i} $$ 其中 $C_0 = 1$(初始值)。引用[3]给出了闭式形式:$h(n) = \binom{2n}{n} - \binom{2n}{n-1}$。 - **应用场景**:计算 $n$ 对括号的合法序列数(如 $n=3$ 时,$C_3 = 5$)。 - **代码实现(C++)**: ```cpp #include <iostream> using namespace std; const int mod = 1e9+7; ll dp[maxn]; // dp[n] 存储卡特兰数 C_n int main() { int n; cin >> n; dp[0] = 1; // 初始值 for (int i = 1; i <= n; i++) { dp[i] = 0; for (int j = 0; j < i; j++) { dp[i] += dp[j] * dp[i-j-1]; // 递推公式:基于子问题求和 } } cout << dp[n] << endl; } ``` - **分析**:时间复杂度 $O(n^2)$,但引用[3]中的闭式优化为 $O(n)$,展示了递推公式在组合数学中的高效性[^3]。 **示例4: 最长公共子序列(字符串算法示例)** 在文本处理中,递推公式用于求解两个序列的公共部分。参考引用[4],该问题求两个字符串的最长公共子序列长度。 - **递推公式**: $$ \text{dp}[i][j] = \begin{cases} \text{dp}[i-1][j-1] + 1 & \text{if } \text{text1}[i-1] = \text{text2}[j-1] \\ \max(\text{dp}[i-1][j], \text{dp}[i][j-1]) & \text{otherwise} \end{cases} $$ 其中 $\text{dp}[i][j]$ 表示字符串 $\text{text1}$ 的前 $i$ 个字符和 $\text{text2}$ 的前 $j$ 个字符的LCS长度,初始条件 $\text{dp}[0][j] = 0$ 和 $\text{dp}[i][0] = 0$。 - **应用场景**:DNA序列比对或代码差异分析(LeetCode 1143)。引用[4]中的代码实现: ```cpp int longestCommonSubsequence(string text1, string text2) { vector<vector<int>> dp(text1.size() + 1, vector<int>(text2.size() + 1, 0)); for (int i = 1; i <= text1.size(); i++) { for (int j = 1; j <= text2.size(); j++) { if (text1[i-1] == text2[j-1]) { dp[i][j] = dp[i-1][j-1] + 1; // 字符匹配时递推 } else { dp[i][j] = max(dp[i-1][j], dp[i][j-1]); // 不匹配时取最大值 } } } return dp[text1.size()][text2.size()]; } ``` - **分析**:时间复杂度 $O(mn)$($m,n$ 为字符串长度),空间复杂度可优化。该公式高效处理重叠子问题,适用于不相交的线等问题[^4]。 #### 3. 总结 递推公式是连接离散序列的桥梁,通过定义项间关系简化复杂问题。在数学中,它用于序列生成(如斐波那契数列);在算法中,它是动态规划的基石(如背包问题和LCS)。关键优势包括: - **高效性**:避免重复计算,尤其在使用记忆化或DP数组时。 - **灵活性**:适用于各种领域,从基础数学到AI优化。 - **局限性**:需要明确初始条件,且递归深度可能导致栈溢出(通常用迭代优化)。 通过以上示例,您可以看到递推公式如何将问题分解为可管理的子问题。如果您有特定场景(如具体算法或数学问题),我可以提供更针对性的解释。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值