部分资源来自AcWing
1.数字三角形
链接:http://acm.hdu.edu.cn/showproblem.php?pid=2084
这是一道比较简单的线性DP问题,把f [ i, j ] 分成从左边来的和从上边来的两类就可以了,下面是代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <limits.h>
#define N 110
using namespace std;
int t, n;
int g[N][N];
int f[N][N];
int main()
{
cin >> t;
while(t--)
{
cin >> n;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= i; j++)
cin >> g[i][j];
for (int i = 0; i <= n; i++)
for (int j = 0; j <= i+1; j++)
f[i][j] = -INT_MAX;
f[1][1] = g[1][1];
for (int i = 2; i <= n; i++)
for (int j = 1; j <= i; j++)
{
f[i][j] = max(f[i-1][j-1], f[i-1][j]) + g[i][j];
}
int m = -INT_MAX;
for (int i = 1; i <= n; i++)
m = max(m, f[n][i]);
cout << m << endl;
}
return 0;
}
2.最长上升子序列
https://www.acwing.com/activity/content/problem/content/1003/1/
状态 f [ i ] 表示的是只考虑前 i 个元素,从中选出的上升子序列(要求必包含第 i 个元素)所构成的集合所具有的最大长度。f [ i ] 的划分标准如下:按照子序列中的倒数第二个元素是原数列中第 0 (表示该子序列只存在一个元素) , 1, 2, 3,… ,i - 1 个元素来划分。代码如下:
#include <iostream>
#include <algorithm>
#include <climits>
using namespace std;
const int N = 1010;
int list[N], f[N];
int n;
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
cin >> list[i];
for (int i = 1; i <= n; i++)
{
f[i] = 1; // f[i] 最小值必为1
for (int j = 1; j <= i - 1; j++)
{
if(list[j] < list[i]) // 构成上升子序列的充分条件
f[i] = max(f[i], f[j] + 1);
}
}
int res = 0; // f[i] 必然大于0
for (int i = 1; i <= n; i++)
res = max(res, f[i]);
cout << res << endl;
return 0;
}
但其实这样做时间复杂度难免有点高,在最坏的情况下可以达到n^2,当数据范围较大时会 tle,比如下面这道题:
https://www.acwing.com/problem/content/898/
仅仅是改了一下数据范围,我们就需要重新考虑一下,看看之前的这个状态计算方法是否有些冗余。
首先对于这样的例子:3,1,2,4,5 我们可以发现,所有可以与3构成上升子序列的数也一定可以与1构成上升子序列,而且我们很明显可以知道,用 1 来领导子序列比用 3 来领导更有发展潜力,比如 2 就可以接在 1 后面,而不能再接在 3 的后面。因此,我们实际上可以只存储具有最小结尾数字的不同长度的上升子序列。
再者,我们可以证明以子序列的长度为下标,以该长度的最小的结尾数字为值的数组 q [ n ] 一定时单调递增的。(用反证法)
有了这些基础,我们大概可以理一下思路:从第 1 个元素开始遍历原始数组 a 的每一个元素,针对每一个 a [ i ],我们在 q 数组中查找一个最大的小于 a [ i ] 的元素 q [ j ] ,那么这个 a [ i ] 就可以接在 q [ j ] 之后,构成一个长度为 j + 1 的子序列,并且其结尾元素 a [ i ] < q [ j + 1 ] ,于是我们把 q [ j + 1 ] 更新成 a [ i ] , 然后如此循环即可。
再说查找,由于 q 数组是严格递增的,我们就可以采用二分查找的方法。代码如下:
#include <iostream>
#include <cstdio>
#include <algorithm>
#define N 100010
using namespace std;
int n;
int a[N], q[N];
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
int len = 0;
q[0] = -2e9;
for (int i = 1; i <= n; i++)
{
int l = 0, r = len;
while(l < r)
{
int mid = (r + l + 1) / 2; // 注意由于区间的变化为 l = mid,因此需要向上取整
if(q[mid] < a[i]) l = mid;
else r = mid - 1;
}
len = max(len, r + 1); // r + 1 为得到的新的子序列的长度,且此时 l == r
q[r + 1] = a[i];
}
cout << len << endl;
return 0;
}
来源:CSDN
作者:Victayria
链接:https://blog.csdn.net/Victayria/article/details/104065595