简单动态规划问题

文章列举并详细解释了多个使用动态规划解决的计算机科学问题,包括最短路、最长上升子序列、最长公共子序列、最长回文子串等。每个问题都提供了问题描述、问题分析、以及相应的代码实现,展示了解决这类问题的思路和方法。

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

简单动态规划问题

一.最短路

1.题目

题目:给定 nnn 个点 mmm 条边的有向图,每条边有个边权,代表经过这条边需要花费的时间,我们只能从编号小的点走到编号大的点,问从 111 号点走到 nnn 号点最少需要花费多少时间?

输入格式

第一行两个整数 n,mn,mn,m

接下来 mmm 行,每行三个整数 u,v,wu,v,wu,v,w,表示存在一条从 uuuvvv 的边权为 www的有向边。

保证存在一条 111nnn 的路径。

输出格式

输出一个数,表示答案。

2.分析

最优子结构:为了算出从 111 号到 yyy 号点最少需要花费多少时间,我们可以先计算出所有和 yyy 号点有边相连,并且标号小于 yyy 的点 xxx,从 111 号点到 xxx 号点最少需要花费多少时间,然后再推导 yyy 号点的情况;

无后效性:我们只关心到每个点最少花费多少时间,不关心具体走了哪条路径。

状态:用 f[i]f[i]f[i] 表示从 111 号点到 iii 号带你最少需要花费多少时间。
转移:假设我们已经知道了 f[x]f[x]f[x] 的值,并且存在一条从 xxxyyy 的代价为 zzz 的边,那么有 f[y]f[y]f[y] = min(f[y]f[y]f[y],f[x]+zf[x]+zf[x]+z);

3.代码

#include <bits/stdc++.h>
using namespace std;

int a[1001][1001], f[1001], n, m;

int main()
{
    scanf("%d%d", &n, &m);
    memset(a, 127, sizeof(a));
    for (int i = 1; i <= m; i++)
    {
        int x, y, z;
        scanf("%d%d%d", &x, &y, &z);
        a[x][y] = min(a[x][y], z);
    }
    memset(f, 127, sizeof(f));
    f[1] = 0;
    for (int i = 2; i <= n; i++)
        for (int j = 1; j < i; j++)
            if (f[j] < 1 << 30 && a[j][i] < 1 << 30)
                f[i] = min(f[i], f[j] + a[j][i]);
    printf("%d\n", f[n]);
}

二.最长上升子序列

1.题目

给定一个长度为 nnn 的数组 a1,a2,…,ana_1,a_2,…,a_na1,a2,,an,问其中的最长上升子序列的长度。也就是说,我们要找到最大的 mmm 以及数组 p1,p2,…,pmp_1,p_2,…,p_mp1,p2,,pm,满足 1≤p1<p2<⋯<pm≤n1≤p_1<p_2<⋯<p_m≤n1p1<p2<<pmn 并且 ap1<ap2<⋯<apma_{p_1}<a_{p_2}<⋯<a_{p_m}ap1<ap2<<apm

输入格式

第一行一个数字 nnn

接下来一行 nnn 个整数 a1,a2,…,ana_1,a_2,…,a_na1,a2,,an

输出格式

一个数,表示答案。

2.分析

状态:f[i]f[i]f[i] 表示以 iii 这个位置结尾的最长上升子序列的长度。
转移:对于一个位置 iii,为了计算 f[i]f[i]f[i],我们枚举子序列中上一个元素的位置 jjj 满足 j<ij<ij<i 并且 aj<aia_j < a_iaj<ai,有 f[i]=max(f[i],f[j]+1)f[i]=max(f[i],f[j]+1)f[i]=max(f[i],f[j]+1)

3.代码

#include <iostream>
using namespace std;
int n, a[1005], ans, f[1005];
int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    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 = 1; i <= n; i++)
        ans = max(ans, f[i]);
    cout << ans;
}

三、最长公共子序列

1.题目

给定一个长度为 nnn 的数组 a1,a2,…,ana_1,a_2,…,a_na1,a2,,an 以及一个长度为 mmm 的数组 b1,b2,…,bmb_1,b_2,…,b_mb1,b2,,bm,问 aaabbb 的最长公共子序列的长度。

也就是说,我们要找到最大的 kkk 以及数组 p1,p2,…,pkp_1,p_2,…,p_kp1,p2,,pk,数组 l1,l2,…,lkl_1,l_2,…,l_kl1,l2,,lk 满足 1≤p1<p2<⋯<pk≤n1≤p_1<p_2<⋯<p_k≤n1p1<p2<<pkn 并且 1≤l1<l2<⋯<lk≤m1≤l_1<l_2<⋯<l_k≤m1l1<l2<<lkm 并且对于所有的 i(1≤i≤k)i(1≤i≤k)i(1ik)api=blia_{p_i}=b_{l_i}api=bli

输入格式

第一行两个整数 n,mn,mn,m

接下来一行 nnn 个整数,a1,a2,…,ana_1,a_2,…,a_na1,a2,,an

接下来一行 mmm 个整数,b1,b2,…,bmb_1,b_2,…,b_mb1,b2,,bm

输出格式

输出一个整数,表示答案。

2.分析

匹配 ai=bja_i=b_jai=bj,考虑取 aaa 中前 i−1i-1i1 个元素和 bbb 中前 j−1j-1j1 个元素情况下的最优解

不匹配 aia_iaibjb_jbj,考虑取 aaa 中前 i−1i-1i1 个元素和 bbb 中前 jjj 个元素情况下的最优解,即 aia_iai 不取。考虑取 aaa 中前 iii 个元素和 bbb 中前 j−1j-1j1 个元素情况下的最优解,即 bjb_jbj 不取 。

3.代码

#include <iostream>
using namespace std;
int n, m, a[1005], b[1005], ans, f[1005][1005];
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    for (int i = 1; i <= m; i++)
        cin >> b[i];
    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 - 1][j - 1] + 1, f[i][j]);
            ans = max(ans, f[i][j]);
        }
    cout << ans;
}

三、最长回文子串

1.题目

给定一个长度为 nnn 的数组 a1,a2,…,ana_1,a_2,…,a_na1,a2,,an,问其中的最长回文子串长度。

定义子串 al,al+1,…,ara_l,a_{l+1},…,a_ral,al+1,,ar 为回文子串,当且仅当这个子串正着看和反着看是一样的,即有 al=ar,al+1=ar−1,…a_l=a_r,a_{l+1}=a_{r−1},…al=ar,al+1=ar1,

输入格式

第一行一个整数 nnn,表示元素个数。

接下来一行,nnn 个整数,a1,a2,…,ana_1,a_2,…,a_na1,a2,,an

输出格式

一个整数,表示答案。

2.分析

我们用 dp[i][j]dp[i][j]dp[i][j]表示从 iiijjj 是否是一个回文序列。如何 a[i]a[i]a[i]a[j]a[j]a[j] 不相等的话我们直接将 dop[i][j]dop[i][j]dop[i][j] 赋值为 000 即可。然后是判断相等的时候,如果 j−i<3j-i<3ji<3,即序列长度是 222333,这种我们是能直接判断出来的,赋值为 111 即可。序列再长的话只能通过短的序列;来转移,即 dp[i][j]=dp[i+1][j−1]dp[i][j]=dp[i+1][j-1]dp[i][j]=dp[i+1][j1],因为 dp[i+1][j−1]dp[i+1][j-1]dp[i+1][j1] 我们是已知的。

具体看代码。

3.代码

#include <iostream>
using namespace std;
int n;
int a[1005], ans = 1;
bool dp[1005][1005];
int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i], dp[i][i] = 1;
    for (int j = 2; j <= n; j++)
        for (int i = 1; i < j; i++)
        {
            if (a[i] != a[j])
            {
                dp[i][j] = 0;
            }
            else
            {
                if (j - i < 3)
                    dp[i][j] = 1;
                else
                {
                    dp[i][j] = dp[i + 1][j - 1];
                }
            }
            if (dp[i][j] && j - i + 1 > ans)
            {
                ans = j - i + 1;
            }
        }
    cout << ans;
}

四、方格取数

1.题目

Hello Kitty想摘点花生送给她喜欢的米老鼠。

她来到一片有网格状道路的矩形花生地(如下图),从西北角进去,东南角出来。

地里每个道路的交叉点上都有种着一株花生苗,上面有若干颗花生,经过一株花生苗就能摘走该它上面所有的花生。

Hello Kitty只能向东或向南走,不能向西或向北走。

问Hello Kitty最多能够摘到多少颗花生。

每组数据的第一行是两个整数,分别代表花生苗的行数 RRR 和列数 CCC

每组数据的接下来 RRR 行数据,从北向南依次描述每行花生苗的情况。每行数据有 CCC 个整数,按从西向东的顺序描述了该行每株花生苗上的花生数目 MMM

输入格式

第一行是一个整数 TTT,代表一共有多少组数据。

接下来是T组数据。

每组数据的第一行是两个整数,分别代表花生苗的行数R和列数 CCC

每组数据的接下来 RRR 行数据,从北向南依次描述每行花生苗的情况。每行数据有 CCC 个整数,按从西向东的顺序描述了该行每株花生苗上的花生数目 MMM

输出格式

对每组输入数据,输出一行,内容为Hello Kitty能摘到得最多的花生颗数。

2.分析

从集合角度来考虑。

状态表示:
1.集合:所有从(1,1) 走到 (i,j)的路线
2.属性:Max/Min/数量

状态计算–集合的划分:
最后一步从上面下来f[i−1][j]+w[i,j]f[i-1][j]+w[i,j]f[i1][j]+w[i,j]
最后一步从左边过来f[i][j−1]+w[i,j]f[i][j-1]+w[i,j]f[i][j1]+w[i,j]

划分重要依据:“最后一步”
集合划分原则:不漏不重(不重有时候可忽略)

3.代码

#include <bits/stdc++.h>

using namespace std;

const int N = 110;

int n, m;
int w[N][N];
int f[N][N];

int main()
{
    int T;
    scanf("%d", &T);
    while (T--)
    {
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++)
                scanf("%d", &w[i][j]);
        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]) + w[i][j];
        printf("%d\n", f[n][m]);
    }
    return 0;
}

五、最低通行费

1.题目

一个商人穿过一个 N×NN×NN×N 的正方形的网格,去参加一个非常重要的商务活动。

他要从网格的左上角进,右下角出。

每穿越中间 111 个小方格,都要花费 111 个单位时间。

商人必须在 (2N−1)(2N-1)(2N1) 个单位时间穿越出去。

而在经过中间的每个小方格时,都需要缴纳一定的费用。

这个商人期望在规定时间内用最少费用穿越出去。

请问至少需要多少费用?

注意:不能对角穿越各个小方格(即,只能向上下左右四个方向移动且不能离开网格)

输入格式

第一行是一个整数,表示正方形的宽度 NNN

后面 NNN 行,每行 NNN 个不大于 100100100 的整数,为网格上每个小方格的费用。

输出格式

输出一个整数,表示至少需要的费用

2.分析

时间不超过 2N−12N-12N1 说明不能走回头路。直接套上一题即可。因为是最小值,所以细节上有点区别。

3.代码

#include <bits/stdc++.h>

using namespace std;

const int N = 110, INF = 1e9;

int n;
int w[N][N];
int f[N][N];

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            scanf("%d", &w[i][j]);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            if (i == 1 && j == 1)
                f[i][j] = w[i][j];
            else
            {
                f[i][j] = INF;
                if (i > 1)
                    f[i][j] = min(f[i][j], f[i - 1][j] + w[i][j]);
                if (j > 1)
                    f[i][j] = min(f[i][j], f[i][j - 1] + w[i][j]);
            }
    printf("%d\n", f[n][n]);
    return 0;
}

六、方格取数

1.题目

设有 N∗NN*NNN 的方格图(N≤10N \le 10N10),我们将其中的某些方格中填入正整数,而其他的方格中则放入数字 000

某人从图的左上角的 AAA(1,1)(1,1)(1,1)出发,可以向下行走,也可以向右走,直到到达右下角的 BBB(N,N)(N,N)(N,N)。在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字 000)。

此人从 AAA 点到 BBB 点共走两次,试找出 222 条这样的路径,使得取得的数之和为最大。

输入格式
第一行为一个整数 NNN ,表示 N×NN×NN×N 的方格图。

接下来的每行有三个整数,第一个为行号数,第二个为列号数,第三个为在该行、该列上所放的数。

行和列编号从 111 开始。
一行“0 0 0”表示结束。

输出格式
输出一个整数,表示两条路径上取得的最大的和。

2.分析

假设从起点同时走,走两条路线。我们用三维表示即可。f[k,i1,i2]f[k,i1,i2]f[k,i1,i2]kkk 表示横纵坐标之和,i1i1i1i2i2i2 分别表示两条路线的横坐标。同一个格子的值只能取一次(因为取一次之后就会变成 000,虽然你第二条路线也能经过,但是增加的值为 000),所以有四种转移的方式:(下、下),(下、右),(右、下),(右、右)。这样,代码就很好写了。

3.代码

#include <bits/stdc++.h>
using namespace std;
int n;
int w[20][20];
int dp[30][20][20];
int main()
{
    cin >> n;
    int a, b, c;
    while (cin >> a >> b >> c, a || b || c)
        w[a][b] = c;
    for (int k = 1; k <= 2 * n; k++)
        for (int i1 = 1; i1 <= n; i1++)
            for (int i2 = 1; i2 <= n; i2++)
            {
                int j1 = k - i1;
                int j2 = k - i2;
                if (j1 >= 1 && j1 <= n && j2 >= 1 && j2 <= n)
                {
                    int t = w[i1][j1];
                    if (i1 != i2)//判断是不是重合了,重合了加一次即可
                        t += w[i2][j2];
                    int &x = dp[k][i1][i2];//太长了,用引用来替换
                    x = max(x, dp[k - 1][i1 - 1][i2 - 1] + t);
                    x = max(x, dp[k - 1][i1][i2 - 1] + t);
                    x = max(x, dp[k - 1][i1 - 1][i2] + t);
                    x = max(x, dp[k - 1][i1][i2] + t);
                }
            }
    cout << dp[2 * n][n][n];
}

七、登山

1.题目

五一到了,PKU-ACM队组织大家去登山观光,队员们发现山上一个有N个景点,并且决定按照顺序来浏览这些景点,即每次所浏览景点的编号都要大于前一个浏览景点的编号。同时队员们还有另一个登山习惯,就是不连续浏览海拔相同的两个景点,并且一旦开始下山,就不再向上走了。队员们希望在满足上面条件的同时,尽可能多的浏览景点,你能帮他们找出最多可能浏览的景点数么?

输入

Line 1: NNN 景点数

Line 2: NNN 个整数,每个景点的海拔

输出

最多能浏览的景点数

2.分析

先单调上升,再单调下降,即求一个山峰形状的,让两边相加最大。我们用 fififi 表示从 111iii 上升子序列的最大值,用 gigigi 表示从 nnniii 上升子序列的最大值。可以先预处理 fffggg 数组,然后再求和。

3.代码

#include<bits/stdc++.h>

using namespace std;

const int N = 1010;

int n, a[N], f[N], g[N];

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n;i++)
        cin >> a[i];
    for (int i = 1; i <= n;i++)
    {
        f[i] = 1;
        for (int j = 1; j < i;j++)
        if(a[i]>a[j])
            f[i] = max(f[i], f[j] + 1);
    }
    for (int i = n; i;i--)
    {
        g[i] = 1;
        for (int j = n; j > i;j--)
        {
            if(a[i]>a[j])
            g[i] = max(g[i], g[j] + 1);
        }
    }
    int res = 0;
    for (int i = 1; i <= n;i++)
        res = max(res, f[i] + g[i] - 1);
    cout << res;
}

八、友好城市

1.题目

Palmia国有一条横贯东西的大河,河有笔直的南北两岸,岸上各有位置各不相同的N个城市。北岸的每个城市有且仅有一个友好城市在南岸,而且不同城市的友好城市不相同。

每对友好城市都向政府申请在河上开辟一条直线航道连接两个城市,但是由于河上雾太大,政府决定避免任意两条航道交叉,以避免事故。编程帮助政府做出一些批准和拒绝申请的决定,使得在保证任意两条航线不相交的情况下,被批准的申请尽量多。

【输入】

111 行,一个整数 NNN,表示城市数。

222 行到第 n+1n+1n+1 行,每行两个整数,中间用 111 个空格隔开,分别表示南岸和北岸的一对友好城市的坐标。

【输出】

仅一行,输出一个整数,表示政府所能批准的最多申请数。

2.分析

我们假设下边的结点是因变量,上边的结点是自变量。先按照因变量的顺序对上面所对应结点进行排序,就比如底下这个结点是 111 号,那么它对应上边的结点的序号就标位 111

为了保证不交叉,假设我们在下面选好了一组点,就要保证上面与之对应的那一组点是上升的,不然就会交叉。(自己画下图可能会好点)

所以还是转换成了最长上升子序列的问题,我们就找已经标好序号的上面点的最长上升子序列即可。

3.代码

#include <bits/stdc++.h>

using namespace std;

typedef pair<int, int> PII;

const int N = 5010;

int n;
PII q[N];
int f[N];

int main()
{
    cin >> n;
    for (int i = 0; i < n; i++)
        cin >> q[i].first >> q[i].second;
    sort(q, q + n);
    int res = 0;
    for (int i = 0; i < n; i++)
    {
        f[i] = 1;
        for (int j = 0; j < i; j++)
            if (q[i].second > q[j].second)
                f[i] = max(f[i], f[j] + 1);
        res = max(res, f[i]);
    }
    cout << res;
}

九、拦截导弹

1.题目

某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。 输入导弹依次飞来的高度,计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

输入格式

一行,空格隔开的若干个正整数。

输出格式

两行,每行一个整数。 第一个整数表示最多能拦截的导弹数; 第二个整数表示要拦截所有导弹最少要配备的系统数。

2.分析

问题 111:最多能拦截多少导弹。可以转换为最长不上升子序列。

问题 222:求需要多少个系统。

贪心流程:
从前往后扫描每个数,对于每个数:
情况 111:如果现有的子序列的结尾都小于当前数,则创建新子序列
情况 222:将当前数放到结尾大于等于它的最小的子序列后面

显然,问题可以转换为最少用多少个非上升子序列就可以把整个序列覆盖掉,如果画图的话就可以显然的得出这就是求最长上升子序列。

3.代码

#include <bits/stdc++.h>

using namespace std;

const int N = 1010;

int n;
int q[N];
int f[N], g[N];

int main()
{
    while (cin >> q[n])
        n++;
    int res = 0;
    for (int i = 0; i < n; i++)
    {
        f[i] = 1;
        for (int j = 0; j < i; j++)
            if (q[j] >= q[i])
                f[i] = max(f[i], f[j] + 1);
        res = max(res, f[i]);
    }
    cout << res << endl;
    int cnt = 0;
    for (int i = 0; i < n; i++) // 这里用贪心来做
    {
        int k = 0;
        while (k < cnt && g[k] < q[i])
            k++;
        g[k] = q[i];
        if (k >= cnt)
            cnt++;
    }
    cout << cnt << endl;
    return 0;
}

十、导弹防御系统

1.题目

为了对抗附近恶意国家的威胁,R 国更新了他们的导弹防御系统。

一套防御系统的导弹拦截高度要么一直 严格单调 上升要么一直 严格单调 下降。

例如,一套系统先后拦截了高度为 333 和高度为 444 的两发导弹,那么接下来该系统就只能拦截高度大于 444 的导弹。

给定即将袭来的一系列导弹的高度,请你求出至少需要多少套防御系统,就可以将它们全部击落。

输入形式】

每组输入有两行,

第一行,输入雷达捕捉到的敌国导弹的数量 kkk(k<=25),

第二行,输入 kkk 个正整数,表示 kkk 枚导弹的高度,按来袭导弹的袭击时间顺序给出,以空格分隔。

【输出形式】

每组输出只有一行,包含一个整数,表示最多能拦截多少枚导弹

2.分析

贪心有两种选择,可以选择放在上升子序列后面,也可以选择放在下降子序列后面,所以只能用爆搜来做。具体实现看代码。

3.代码

#include <bits/stdc++.h>

using namespace std;

const int N = 55;

int n;
int q[N];
int up[N], down[N];
int ans;

void dfs(int u, int su, int sd) // u是当前枚举的数,su是上升子序列个数,sd是下降子序列个数
{
    if (su + sd >= ans)
        return;
    if (u == n)
    {
        ans = su + sd;
        return;
    }

    // 情况1:将当前数放到上升子序列中
    int k = 0;
    while (k < su && up[k] >= q[u])
        k++;
    int t = up[k];
    up[k] = q[u];
    if (k < su)
        dfs(u + 1, su, sd);
    else
        dfs(u + 1, su + 1, sd);
    up[k] = t;
    // 情况2:将当前数放到下降子序列中
    k = 0;
    while (k < sd && down[k] <= q[u])
        k++;
    t = down[k];
    down[k] = q[u];
    if(k<sd)
        dfs(u + 1, su, sd);
    else
        dfs(u + 1, su, sd);
    down[k] = t;
}
int main()
{
    while (cin >> n, n)
    {
        for (int i = 0; i < n; i++)
            cin >> q[i];
        ans = n;
        dfs(0, 0, 0);
        cout << ans << endl;
    }
}

十一、最长公共上升子序列

1.题目

给定两个长度为 nnn 的数组 a[n]a[n]a[n]b[n]b[n]b[n] ,求两个数组的 最长公共上升子序列长度。

2.分析

f[i,j]f[i,j]f[i,j] 集合:所有由第一个序列的前 iii 个字母,和第二个序列的前 jjj 个字母构成的,且以 b[j]b[j]b[j] 结尾的公共上升子序列。

属性:MaxMaxMax

状态计算:
所有包含 a[i]a[i]a[i] 的公共上升子序列:将其分成一块一块的,我们可以根据倒数第二个数的位置来划分(因为最后一个数已经固定了),即可以分成:空、b[1]、b[2]、...、b[j−1]b[1]、b[2]、...、b[j-1]b[1]b[2]...b[j1](当然其中可能有不成立的)。
所有不包含 a[i]a[i]a[i] 的公共上升子序列:f[i−1][j]f[i-1][j]f[i1][j]

具体看代码。

3.代码

不优化( O3O^{3}O3 )

#include <bits/stdc++.h>

using namespace std;

const int N = 3010;

int n;
int a[N], b[N];
int f[N][N];

int main()
{
    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]; // 不考虑a[i]
            if (a[i] == b[j])      // 考虑a[i]需要满足相等条件
            {
                f[i][j] = max(f[i][j], 1); // 取空的时候
                for (int k = 1; k < j; k++)
                {
                    if (b[k] < b[j])
                        f[i][j] = max(f[i][j], f[i][k] + 1);
                }
            }
        }
    int res = 0;
    for (int i = 1; i <= n; i++)
        res = max(res, f[n][i]);
    cout << res;
}

优化:因为 a[i]a[i]a[i]b[j]b[j]b[j] 相等,所以可以求个前缀最大值。

#include <bits/stdc++.h>

using namespace std;

const int N = 3010;

int n;
int a[N], b[N];
int f[N][N];
int g[N][N]; // 表示满足a[i]>b[j]的所有f[i][j]+1的最大值

int main()
{
    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++)
    {
        g[i][0] = 1;
        for (int j = 1; j <= n; j++)
        {
            f[i][j] = f[i - 1][j];
            if (a[i] == b[j])
                f[i][j] = max(f[i][j], g[i][j - 1]);
            g[i][j] = g[i][j - 1];
            if (b[j] < a[i])
                g[i][j] = max(g[i][j], f[i][j] + 1);
        }
    }
    int res = 0;
    for (int i = 1; i <= n; i++)
        res = max(res, f[n][i]);
    cout << res;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值