题目描述
由于你帮助 Alice 回答得非常好,Sept 又找到了 Bob,希望能难倒他。
他给了要求 Bob 组成一个长度为 n 的新的数列 a,其中数列 a 的每一个元素 都有 k 个取值。
求所有可能的数列 a 中的最长上升子序列的最大长度。
由于 Sept 怕题目钛难,所以他答应 Bob,对于每个 i,k 个取值不降。
输入描述
第一行两个数 k,n,意义如题述。
接下来 n 行,每行 k 个数,即 的 k 个取值。
输出描述
仅一行一个整数,即所有可能的数列 a 中的最长上升子序列的最大长度。
示例输入
2 2
1 3
1 2
示例输出
2
思路
"最长上升子序列长度",从题目描述中捕捉到这几个词,DNA就动了!
我们很自然地会从最长上升子序列解题方向考虑这道题。
但由题目我们可知,这n个数都有k个可能的取值,
也就是说,这n个数的具体取值是待定的,所以我们无法通过直接套用最长上升子序列的解题模板解题。
从动态规划的思想出发,我们很容易想到的思路便是枚举每一个的取值,进行动态规划。如下代码所示:
f[i][j] = 0 # f[i][j] 表示以a[i][j]结尾的最长上升子序列长度 for i in range(n): for j in range(k): for p in range(i): for q in range(k): if A[i][j] > A[p][q]: f[i][j] = max(f[i][j],f[p][q] + 1)
思路是对的,但四层循环,对于的数量级,毫无疑问会超时。
每一个就占据数组的一个维度,n个便是二维数组.
对于这种二维数组,我们遇到的一个困难便是难以使用的上升子序列算法,而只能使用基于朴素最长上升子序列的复杂度为算法。
因此,我们需要考虑是否能将二维数组降到一维,如何降维?
因为“对于每一个i,k个值不降”,如果将每一行以从大到小存入一个一维数组,
例如对于样例输入,a = [3,1,2,1]
,那么对于任意一个上升子序列,至多只会取到的一个值(可以用反证法证得)。
于是,问题转变为如何从一个以为数组中求最长上升子序列长度了。
套用一个复杂度为O(nlogn)
的算法即可。
代码
python3
k,n = map(int,input().split()) A = [] for i in range(n): T = list(map(int,input().split())) T.reverse() A += T ln = 0 f = [0] * (len(A) + 1) f[0] = int(-2e9) for i in range(len(A)): l = 0 r = ln while l < r: mid = l + r + 1 >> 1 if f[mid] >= A[i]: r = mid - 1 else: l = mid f[r + 1] = A[i] ln = max(ln,r + 1) print(ln)
C++
#include<iostream> #include<algorithm> using namespace std; const int N = 1010, M = 5000 * 1000 + 10; int f[N]; int A[M]; int main(){ int n,k; scanf("%d%d",&k,&n); for(int i = 0;i < n;i++){ for(int j = k - 1;j >= 0;j--){ scanf("%d",&A[i * k + j]); } } // LIS int len = 0; f[0] = -2e9; for(int i = 0,m = k * n;i < m;i++){ int l = 0,r = len; while(l < r){ int mid = l + r + 1 >> 1; if(f[mid] >= A[i]) r = mid - 1; else l = mid; } f[r + 1] = A[i]; len = max(len,r + 1); } printf("%d\n",len); return 0; }
ps
- 存储长度为i的上升子序列的第i个数(该数为所有可能的取值中的最小值)
- 使用python代码实现本题仍会有超时(超过2000ms)的情况,使用C++(280ms)实现不会出现此问题。对于python效率问题,我暂时想不到如何从算法上或者代码上做进一步提升——不过如果用PyPy代替Python进行编译。
- 对题意做好理解再解题。
题目让我迷惑了好久