动态规划02

线性 dp--最长上升子序列模型

最长上升子序列

题目链接:
B3637 最长上升子序列 - 洛谷
思路:

f[i] 表示所有以 a[i] 结尾的严格单调上升子序列长度,初始时 f[i]=1 ,枚举序列的倒数第二个数,用 j(0<j<i) 来表示,若 a[j]<a[i]f[i]=max(f[i],f[j]+1)
代码:

cpp 复制代码
const int N=5500;
int a[N];
int f[N];
void solve()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        f[i]=1;
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<i;j++)
        {
            if(a[i]>a[j])
            f[i]=max(f[i],f[j]+1);
        }
    }
    int ans=0;
    for(int i=1;i<=n;i++)ans=max(ans,f[i]);
    cout<<ans;
}

使用二分查找可以减少时间复杂度,对于新进来的元素 a[i],如果 a[i] 大于 f[cnt],则 f 数组长度加一,将 a[i] 添加进去。反之用 a[i] 替换掉 f 数组中第一个大于或者等于 a[i] 的元素。这样虽然 f 数组中存的不是最长上升子序列,但是长度是相等的。

cpp 复制代码
const int N=1e5+5;
int a[N];
int f[N];
int cnt;
int find(int x){
    int l=1,r=cnt;
    while(l<r){
        int mid=(l+r)>>1;
        if(f[mid]>=x){
            r=mid;
        }else l=mid+1;
    }
    return l;
}
void solve()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }
    f[++cnt]=a[1];
    for(int i=2;i<=n;i++)
    {
        if(f[cnt]<a[i])f[++cnt]=a[i];
        else{
            int idx=find(a[i]);
            f[idx]=a[i];
        }
    }
    cout<<cnt;
}

怪盗基德的滑翔翼

题目链接:
U234151 怪盗基德的滑翔翼 - 洛谷
思路:

这个题其实就是求一个最长下降子序列,可以选任意楼为起点,假如选择 a[i] 为起点,那么最长距离为以 a[i] 为结尾的最长上升子序列。所以可以正向做一次反向再求一次得到答案最大值。
代码:

cpp 复制代码
const int N=1e5+5;
int a[N];
int f[N];
int b[N];
void solve()
{
    int n;
    cin>>n;
    memset(b,0,sizeof b);
    memset(f,0,sizeof f);
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        f[i]=1;
        b[i]=1;//这里用了两个数组可能不好看
    }
    for(int i=n;i>0;i--)
    {
        for(int j=n;j>i;j--){
            if(a[j]<a[i])
            f[i]=max(f[i],f[j]+1);
        }
    }
    int ans=0;
    // for(int i=1;i<=n;i++)
    // {
    //     ans=max(ans,f[i]);
    // }
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<i;j++)
        {
            if(a[j]<a[i])b[i]=max(b[i],b[j]+1);
        }
    }
    for(int i=1;i<=n;i++)
    {
        ans=max(ans,max(f[i],b[i]));
    }
    cout<<ans<<endl;
}

登山

题目链接:
U247946 登山 - 洛谷
思路:

这里的要求一旦开始下降就不能再上升,相邻两个景点的海拔不能相同。按照编号递增顺序来浏览,那么就是先单调上升再单调下降。这样我们就要在中间选一个点,分别向两边走,取最大值。这样的话就先从左向右做一遍最长上升子序列再从右向左做一遍最长上升子序列,再进行枚举答案取最大值即可。
代码:

cpp 复制代码
const int N=1e5+5;
int a[N];
int f[N],b[N];
void solve()
{
    int n;
    cin>>n;
    memset(f,0,sizeof f);
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }
    int ans=0;
    for(int i=1;i<=n;i++)
    {
        f[i]=1;
        for(int j=1;j<i;j++)
        {
            if(a[j]<a[i])f[i]=max(f[i],f[j]+1);
        }
    }
    for(int i=n;i>0;i--){
        b[i]=1;
        for(int j=n;j>i;j--){
            if(a[j]<a[i])b[i]=max(b[i],b[j]+1);
        }
    }
    for(int i=1;i<=n;i++)
    {
        ans=max(ans,f[i]+b[i]-1);
    }
    cout<<ans<<endl;
}

合唱队形

题目链接:
P1091 [NOIP 2004 提高组] 合唱队形 - 洛谷
思路:

这个题目要求最少的出队人数,那么其实和上一题是相似的,我们找到满足这个类型队伍的最大值,用 n 减去即可。
代码:

代码只需要将输出修改为 cout<<n-ans<<endl

友好城市

题目链接:
P2782 友好城市 - 洛谷
思路:

先把一边排序,另一边的最长上升子序列的长度就是最多的的数量,一边已经确定好顺序了,只要保证另一边也是升序,就一定不会交叉。只用 dp 会超时,用二分优化。

同样的用一个数组 f 来存,如果 a[i] 大于 f 最后一个数那么就可以放进去,长度加一。反之则找 f 数组中第一个大于 a[i] 的数替换。
代码:

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define endl '\n'
#define PII pair<int,int>
#define x first
#define y second
const int N=2e5+5;
PII a[N];
int f[N],b[N];
void solve()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i].x;
        cin>>a[i].y;
    }
    sort(a+1,a+1+n);
    int cnt=0;
    f[++cnt]=a[1].y;
    for(int i=2;i<=n;i++)
    {
        if(a[i].y>f[cnt])f[++cnt]=a[i].y;
        else{
            int idx=upper_bound(f+1,f+cnt+1,a[i].y)-f;
            f[idx]=a[i].y;
        }
        
    }
    cout<<cnt;
}
signed main(){
	IOS
	int t=1;
	while(t--)
	{
		solve();
	}
}

最大上升子序列和

题目链接:
T285024 最大上升子序列和 - 洛谷
思路:

这个题目使用 f[i] 表示所有以 a[i] 结尾的上升子序列和的最大值。
代码:

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define endl '\n'
#define PII pair<int,int>
#define x first
#define y second
const int N=2e5+5;
int a[N];
void solve()
{
    int n;
    cin>>n;
    vector<int> f(n+5);
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        f[i]=a[i];
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<i;j++)
        {
            if(a[j]<a[i])f[i]=max(f[i],f[j]+a[i]);
        }
    }
    int ans=*max_element(f.begin(),f.end());
    cout<<ans;
}
signed main(){
	IOS
	int t=1;
	while(t--)
	{
		solve();
	}
}

导弹拦截

题目链接:
P1020 [NOIP 1999 提高组] 导弹拦截 - 洛谷
思路:

第一问求最长不上升子序列。

第二问考虑贪心:对于每个数,如果现有的子序列的结尾都小于当前数字,那么开一个新的子序列。否则将它放到结尾大于等于它的最小子序列的后面。

不会证明 🥱
代码:

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define endl '\n'
#define PII pair<int,int>
#define x first
#define y second
const int N=2e5+5;
int q[N];
int a[N];
int f[N];
int cnt=0;
void solve()
{
    int n=0,x;
    while(cin>>x){
        a[++n]=x;
    }
    f[++cnt]=a[1];
    for(int i=2;i<=n;i++)
    {
        if(f[cnt]>=a[i])f[++cnt]=a[i];
        else{
            int idx=upper_bound(f+1,f+1+cnt,a[i],greater<int>())-f;
            f[idx]=a[i];
        }
    }
    cout<<cnt<<endl;
    memset(q,0x3f,sizeof q);
    cnt=1;
    for(int i=1;i<=n;i++)
    {
        if(q[cnt]>=a[i]){
            int idx=lower_bound(q+1,q+1+cnt,a[i])-q;
            q[idx]=a[i];
        }else{
            q[++cnt]=a[i];
        }
    }
    cout<<cnt;
}
signed main(){
	IOS
	int t=1;
	while(t--)
	{
		solve();
	}
}

最长公共子序列

题目链接:
897. 最长公共子序列 - AcWing题库
思路:

f[i][j] 表示 a 的前 i 个字母,和 b 的前 j 个字母的最长公共子序列长度。

a[i]b[j] 是否在子序列中划分为四类:

  1. a[i] 不在,b[j] 不在 此时 max = f[i-1][j-1]
  2. a[i] 在,b[j] 不在 此时 max=f[i−1][j]
  3. a[i] 不在,b[j] 在 此时 max=f[i][j-1]
  4. a[i] 在,b[j] 在 此时 max = f[i-1][j-1]+1
    1)情况是包含在 2)3)中的。
    代码:
cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define int long long
#define endl '\n'
const int N=1010;
char a[N],b[N];
int n,m;
int f[N][N];
void solve(){
    cin>>n>>m;
    cin>>a+1>>b+1;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            f[i][j]=max(f[i-1][j],f[i][j-1]);
            if(a[i]==b[j])f[i][j]=max(f[i][j],f[i-1][j-1]+1);
        }
    }
    cout<<f[n][m];
}
signed main(){
    IOS
    int t=1;
    while(t--){
        solve();
    }
}

两个排列的最长公共子序列

题目链接:
P1439 两个排列的最长公共子序列 - 洛谷
思路:

这个题目用上面的方法会超时。我们可以把第一个序列中第 i 个数映射为 i,在第二个序列中也进行修改。比如原序列为 A={1,2,5,4,3} B={5,3,4,2,1} 映射后为 A={1,2,3,4,5} B={3,5,4,2,1} 这样将求最长公共子序列问题转为求 B 序列的最长上升子序列。这样就可以用二分优化。
代码:

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define int long long
#define endl '\n'
const int N=1e5+5;
int a[N],b[N];
int n;
int f[N];
void solve(){
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        int x;
        cin>>x;
        a[x]=i;//这样使用A数组存储映射关系,方便B数组查询。
    }
    for(int i=1;i<=n;i++)
    {
        int x;
        cin>>x;
        b[i]=a[x];
    }
    int cnt=0;
    f[++cnt]=b[1];
    for(int i=2;i<=n;i++){
        if(b[i]>f[cnt])f[++cnt]=b[i];
        else{
            int idx=lower_bound(f+1,f+1+cnt,b[i])-f;
            f[idx]=b[i];
        }
    }
    cout<<cnt;
}
signed main(){
    IOS
    int t=1;
    while(t--){
        solve();
    }
}

Missile Defence System

题目链接:
P10490 Missile Defence System - 洛谷
思路:

一个导弹有四种选择:

1)用一个已经建立好的递增拦截装置拦截;

2)用一个已经建立好的递减拦截装置拦截;

3)新建一个递增拦截装置;

4)新建一个递减拦截装置。

考虑深搜 DFS,我们要进行搜索这颗导弹属于上升子序列还是下降子序列,搜索这颗导弹属于哪一个上升/下降子序列。
代码:

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define endl '\n'
#define PII pair<int,int>
#define x first
#define y second
const int N=55;
int up[N],down[N];
int a[N];
int ans;
int n;
//前u个导弹\上升系统个数su\和下降系统个数sd
void dfs(int idx,int su,int sd){
    if(su+sd>=ans)return;
    //最优性剪枝:当目前答案已经大于已知最优答案时,直接回溯
    if(idx==n){
        ans=su+sd;
        return;
    }
    /*
    使用上升系统拦截。
    如果当前已有的上升拦截系统的高度都大于第u个导弹高度,则重新开一套系统
    否则,就找到当前低于第u个导弹最高拦截系统来负责拦截
    */
    int k=0;
    while(k<su&&up[k]>=a[idx])k++;
    int t=up[k];
    up[k]=a[idx];
    if(k>=su)dfs(idx+1,su+1,sd);
    else dfs(idx+1,su,sd);
    up[k]=t;//恢复现场
    /*
    用下降拦截系统来拦截
    如果当前已有的下降拦截系统的高度都小于第u个导弹高度,则重新开一套系统
    否则,则由当前大于第u个导弹最低拦截系统来负责拦截
    */
    k=0;
    while(k<sd&&down[k]<=a[idx])k++;
    t=down[k];
    down[k]=a[idx];
    if(k>=sd)dfs(idx+1,su,sd+1);
    else dfs(idx+1,su,sd);
    down[k]=t;
}
void solve()
{
    while(cin>>n){
        if(n==0)break;
        for(int i=0;i<n;i++)cin>>a[i];
        ans=n;
        dfs(0,0,0);
        cout<<ans<<endl;
    }
}
signed main(){
	IOS
	int t=1;
	while(t--)
	{
		solve();
	}
}

LCIS

题目链接:
P10954 LCIS - 洛谷
思路:

f[i][j] 表示 a 的前 i 个数和 b 的前 j 个数并以 b[j] 结尾的最大长度。

a[i]=b[j] 时,只需要在前面找到一个可以把 b[j] 接上去的最长公共上升子序列,答案为 max(f[i-1][k])+1

a[i]≠b[j] 时,f[i][j]=f[i-1][j]
代码:

cpp 复制代码
/*三重循环做法*/
const int N=3100;
int a[N],b[N];
int n;
int f[N][N];
void solve(){
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=n;i++)cin>>b[i];
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            f[i][j]=f[i-1][j];
            if(a[i]==b[j]){
                int mx=0;
                for(int k=1;k<j;k++){
                    if(b[k]<b[j])
                        mx=max(mx,f[i-1][k]);
                }
                f[i][j]=mx+1;
            }
        }
    }
    int ans=0;
    for(int i=1;i<=n;i++)
    {
        ans=max(ans,f[n][i]);
    }
    cout<<ans;
}
/*
	可以发现每次循环所得到的mx值满足a[i]>b[k]的f[i-1][k]+1的前缀最大值,所以我们直接将这步提出来。
*/
const int N=3100;
int a[N],b[N];
int n;
int f[N][N];
void solve(){
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=n;i++)cin>>b[i];
    for(int i=1;i<=n;i++)
    {
        int mx=0;
        for(int j=1;j<=n;j++)
        {
            if(a[i]!=b[j])f[i][j]=f[i-1][j];
            if(a[i]==b[j])f[i][j]=max(mx,f[i][j])+1;
            if(a[i]>b[j])mx=max(mx,f[i-1][j]);
        }
    }
    int ans=0;
    for(int i=1;i<=n;i++)
    {
        ans=max(ans,f[n][i]);
    }
    cout<<ans;
}
相关推荐
故事和你918 小时前
sdut-程序设计基础Ⅰ-实验五一维数组(8-13)
开发语言·数据结构·c++·算法·蓝桥杯·图论·类和对象
像污秽一样8 小时前
算法与设计与分析-习题4.2
算法·排序算法·深度优先·dfs·bfs
Storynone9 小时前
【Day20】LeetCode:39. 组合总和,40. 组合总和II,131. 分割回文串
python·算法·leetcode
明明如月学长9 小时前
AI 更新太快学不过来?我用OpenClaw打造专属AI学习工作流
算法
黎阳之光9 小时前
【黎阳之光:以无线专网与视频孪生,赋能智慧广电与数字中国】
算法·安全·智慧城市·数字孪生
刀法如飞10 小时前
Agentic AI时代,程序员必备的算法思想指南
人工智能·算法·agent
刀法如飞11 小时前
Agentic AI时代程序员必备算法思想详解(附实战案例)
算法·ai编程·编程开发·agentic
飞Link11 小时前
告别盲目找Bug:深度解析 TSTD 异常检测中的预测模型(Python 实战版)
开发语言·python·算法·bug
记忆多12 小时前
c++名字空间 函数模版 左右值
开发语言·c++·算法
三伏52212 小时前
控制理论前置知识——相平面数学基础2(示例部分)
算法·平面·控制