第一个问题,如何求最长上升序列
题意: n 位同学站成一排,音乐老师要请其中的 n−kn-kn−k 位同学出列,使得剩下的 kkk 位同学排成合唱队形。
合唱队形是指这样的一种队形:设 k 位同学从左到右依次编号为 1,2 … ,k,他们的身高分别为 t1,t2…,tk,则他们的身高满足中间最高两边递减
你的任务是,已知所有 n 位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形
如果我们用常规dp做法的话,时间复杂度为n^2.
for(int i = 1;i <= n; i++)//从1到n求最长升
for(int j = 0; j < i; j++) if(a[i] > a[j])
dp[i] = max(dp[i], dp[j] + 1);
如果用标准求最长上升序列,LIS时间复杂度为n*logn
for(int i = 2; i <= n; i++){
if(a[i] > dp[cnt]) dp[++cnt] = a[i];
else{
int p = lower_bound(dp + 1, dp + cnt + 1, a[i]) - dp;
dp[p] = a[i];
}
}
cout << cnt << endl;
区别:
第一种虽然时间复杂度高,但是他能存储数列中前 n 位数字的最长上升序列
第二种最后只能求 n 位
也就对应了一句俗话,时间复杂度越高的算法,其功能也就越多(不绝对正确)
https://www.luogu.com.cn/problem/P1091
#include <cmath>
#include <cstdio>
#include <string>
#include <cstring>
#include <string.h>
#include <iostream>
#include <algorithm>
using namespace std;
#define ll long long
int a[1000005] = {0};
int dp[2][100005] = {0};
int main() {
int n;
cin >> n;
for(int i = 1; i <= n; i++){
scanf("%d", a + i);
}
for(int i = 1; i <= n; i++)
for(int j = 0; j < i; j++)
if(a[i] > a[j]) dp[0][i] = max(dp[0][i], dp[0][j] + 1);
for(int i = n; i >= 1; i--)
for(int j = n + 1; j > i; j--)
if(a[i] > a[j]) dp[1][i] = max(dp[1][i], dp[1][j] + 1);
int ans = 0;
for(int i = 1; i <= n; i++){
ans = max(ans, dp[0][i] + dp[1][i] - 1);
}
cout << n - ans << endl;
return 0;
}
第二个二重循环是求最长下降序列
直接参照第一个倒过来就行了,注意i,j的初始化问题