最长上升序列、最长下降序列及其变形模板

本文介绍了一种求解最长递增子序列(LIS)问题的高效算法,并通过两个函数分别实现了查找递减序列和递增序列的功能。该算法利用动态规划的思想,结合二分查找提高效率。
#include <bits/stdc++.h>
using namespace std;
#define _for(i,a,b) for(int i=a;i<=b;i++)
const int maxn = 107;
int n,dp1[maxn],dp2[maxn],a[maxn],sum1[maxn],sum2[maxn];
int check(int x,int y)//下降序列
{
    int l = 1;
    int r = y;
    int ans = 0;
    while(l<=r)
    {
        int mid = (l+r)/2;
        if(dp1[mid]<x)
        {
            r = mid-1;
            ans = mid;
        }
        else l = mid+1;
    }
    return ans;
}
int checkgo(int x,int y)//上升序列
{
    int l = 1;
    int r = y;
    int ans = 0;
    while(l<=r)
    {
        int mid = (l+r)/2;
        if(dp2[mid]>=x)
        {
            r = mid-1;
            ans = mid;
        }
        else l = mid+1;
    }
    return ans;
}
int num(int x,int y)
{
    memset(dp1,0,sizeof(dp1));
    int ans = 1;
    dp1[x]=a[x];
    _for(i,x+1,y)
    {
        if(dp1[ans]>a[i])
        {
            dp1[++ans]=a[i];
        }
        else
        {
            int k = check(a[i],ans);
            dp1[k]=a[i];
        }
    }
    return ans;
}
int num2(int x,int y)
{
    memset(dp2,0,sizeof(dp2));
    dp2[x]=a[x];
    int ans2 = 1;
    _for(i,x+1,y)
    {
        if(dp2[ans2]<a[i])
        {
            dp2[++ans2]=a[i];
        }
        else
        {
            int k = checkgo(a[i],ans2);
            dp2[k]=a[i];
        }
    }
    return ans2;
}
int main(int argc, char const *argv[])
{
    cin>>n;
    _for(i,1,n)cin>>a[i];
    int ans = 0;
    _for(i,1,n)sum1[i]=num2(1,i);
    _for(i,1,n)sum2[i]=num(i,n);
    _for(i,1,n)cout<<sum2[i]<<" "<<sum1[i]<<endl;
    return 0;
}

<think>嗯,用户之前问过C++模板,现在又询问动态规划的常见模板。我需要先确定用户的需求是什么。可能用户正在学习算法,或者准备面试,需要掌握动态规划的实现方法。动态规划是一个比较复杂的主题,常见的问题类型有很多,比如背包问题、路径问题、字符串处理等。用户可能需要一些典型的模板来帮助理解和应用。 首先,我应该回顾用户之前的提问历史。用户之前得到了关于C++各种模板的回答,包括基础算法、数据结构、经典算法等。现在用户可能希望深入动态规划这一特定领域。需要确保提供的模板结构清晰,涵盖不同类型的问题,并且有代码示例和注意事项。 接下来,用户可能对动态规划的基本概念有所了解,但需要具体的实现模板。我需要分门别类地介绍常见的动态规划问题类型,比如线性DP、背包问题、区间DP、状态压缩DP等。每个类别下给出典型的例子,比如斐波那契数列、最长递增子序列、0-1背包等,并提供C++的代码模板。 同时,要注意代码的正确性和最佳实践,比如使用vector而不是原生数组,避免不必要的内存开销。还要提到初始化和状态转移方程的注意事项,因为这是动态规划的关键部分。此外,用户可能需要了解如何优化空间复杂度,比如滚动数组的技巧。 另外,用户可能在实际应用中遇到问题,比如如何定义状态,如何推导状态转移方程,或者在处理边界条件时的错误。因此,在注意事项部分需要强调这些点,帮助用户避免常见错误。 最后,应该提供一些学习建议,比如推荐练习题目,或者如何逐步掌握动态规划。这样用户不仅获得模板,还能知道如何应用和进一步学习。需要确保回答结构清晰,代码示例正确,并且语言通俗易懂,避免使用过于专业的术语,让不同层次的学习者都能理解。</think>以下是为您整理的C++动态规划常见模板分类及详解(附典型例题代码实现): --- ### **一、基础线性DP模板** #### 1. 斐波那契数列(一维DP) ```cpp int fib(int n) { if(n <= 1) return n; vector<int> dp(n+1); // 使用vector自动初始化 dp[0] = 0; dp[1] = 1; for(int i=2; i<=n; ++i) { dp[i] = dp[i-1] + dp[i-2]; // 状态转移方程 } return dp[n]; } // 优化空间版(滚动数组) int fib_opt(int n) { if(n <=1) return n; int prev = 0, curr = 1; for(int i=2; i<=n; ++i){ int sum = prev + curr; prev = curr; curr = sum; } return curr; } ``` #### 2. 最长递增子序列(LIS) ```cpp int lengthOfLIS(vector<int>& nums) { vector<int> dp(nums.size(), 1); // 初始化为1 int max_len = 1; for(int i=1; i<nums.size(); ++i){ for(int j=0; j<i; ++j){ if(nums[i] > nums[j]){ dp[i] = max(dp[i], dp[j]+1); // 状态转移 } } max_len = max(max_len, dp[i]); } return max_len; } ``` --- ### **二、背包问题模板** #### 1. 0-1背包(二维DP) ```cpp int knapsack(vector<int>& weights, vector<int>& values, int capacity) { int n = weights.size(); vector<vector<int>> dp(n+1, vector<int>(capacity+1, 0)); for(int i=1; i<=n; ++i){ for(int w=1; w<=capacity; ++w){ if(w < weights[i-1]){ dp[i][w] = dp[i-1][w]; // 无法放入 } else { dp[i][w] = max( // 决策:放或不放 dp[i-1][w], dp[i-1][w-weights[i-1]] + values[i-1] ); } } } return dp[n][capacity]; } // 优化空间版(一维数组倒序遍历) int knapsack_opt(vector<int>& weights, vector<int>& values, int capacity) { vector<int> dp(capacity+1, 0); for(int i=0; i<weights.size(); ++i){ for(int w=capacity; w>=weights[i]; --w){ // 必须倒序 dp[w] = max(dp[w], dp[w-weights[i]] + values[i]); } } return dp[capacity]; } ``` #### 2. 完全背包(一维DP正序遍历) ```cpp int completeKnapsack(vector<int>& weights, vector<int>& values, int capacity) { vector<int> dp(capacity+1, 0); for(int i=0; i<weights.size(); ++i){ for(int w=weights[i]; w<=capacity; ++w){ // 正序遍历 dp[w] = max(dp[w], dp[w-weights[i]] + values[i]); } } return dp[capacity]; } ``` --- ### **三、区间DP模板** #### 矩阵链乘法(区间分割) ```cpp int matrixChainMultiplication(vector<int>& dims) { int n = dims.size()-1; // 矩阵个数 vector<vector<int>> dp(n, vector<int>(n, 0)); for(int len=2; len<=n; ++len){ // 区间长度 for(int i=0; i<=n-len; ++i){ // 起始点 int j = i + len - 1; // 结束点 dp[i][j] = INT_MAX; for(int k=i; k<j; ++k){ // 分割点 int cost = dp[i][k] + dp[k+1][j] + dims[i]*dims[k+1]*dims[j+1]; dp[i][j] = min(dp[i][j], cost); } } } return dp[0][n-1]; } ``` --- ### **四、状态压缩DP模板** #### 旅行商问题(TSP) ```cpp int tsp(vector<vector<int>>& dist) { int n = dist.size(); vector<vector<int>> dp(1<<n, vector<int>(n, INT_MAX/2)); dp[1][0] = 0; // 初始状态:只访问过0号城市 for(int mask=1; mask<(1<<n); ++mask){ for(int u=0; u<n; ++u){ if(!(mask & (1<<u))) continue; // u不在当前状态 for(int v=0; v<n; ++v){ if(mask & (1<<v)) continue; // v已在当前状态 int new_mask = mask | (1<<v); dp[new_mask][v] = min(dp[new_mask][v], dp[mask][u] + dist[u][v]); } } } int final_mask = (1<<n)-1; int res = INT_MAX; for(int u=1; u<n; ++u){ res = min(res, dp[final_mask][u] + dist[u][0]); } return res; } ``` --- ### **五、DP优化技巧** 1. **滚动数组优化**: ```cpp // 原二维数组 vector<vector<int>> dp(n, vector<int>(m)); // 优化为一维 vector<int> dp(m); ``` 2. **斜率优化**(单调队列): ```cpp deque<int> q; // 存储决策点 for(int i=0; i<n; ++i){ while(!q.empty() && check(q.front(), i)) q.pop_front(); dp[i] = calculate(q.front()); while(!q.empty() && better(i, q.back())) q.pop_back(); q.push_back(i); } ``` --- ### **六、注意事项** 1. **四步法解题**: - 定义状态(明确dp数组含义) - 确定状态转移方程 - 初始化边界条件 - 确定遍历顺序 2. **常见错误**: - 数组越界(特别注意索引从0还是1开始) - 未正确初始化(如最小值问题忘记初始化为极大值) - 状态转移条件不完整 3. **调试技巧**: ```cpp // 打印DP表 for(auto& row : dp){ for(int x : row) cout << setw(3) << x; cout << endl; } ``` --- ### **七、经典练习题** 1. 简单:爬楼梯(70)、打家劫舍(198) 2. 中等:最长公共子序列(1143)、零钱兑换(322) 3. 困难:编辑距离(72)、正则表达式匹配(10) 建议从基础模板入手,逐步理解状态设计和转移方程的本质,再挑战复杂变形问题。每个模板类型建议完成3-5道相似题型巩固理解。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值