原题:
1) ZOJ: http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=2136
2) POJ: http://poj.org/problem?id=2533
问题描述:
A numeric sequence of ai is ordered if a1 < a2 < ... < aN. Let the subsequence of the given numeric sequence (a1, a2, ..., aN) be any sequence (ai1, ai2, ..., aiK), where 1 <= i1 < i2 < ... < iK <= N. For example, the sequence (1, 7, 3, 5, 9, 4, 8) has ordered subsequences, e.g., (1, 7), (3, 4, 8) and many others. All longest ordered subsequences of this sequence are of length 4, e.g., (1, 3, 5, 8).
Your program, when given the numeric sequence, must find the length of its longest ordered subsequence.
分析:分两种情况:
第一种情况:考虑输入序列的右边部分m个 元素, 用f(ai,m)表示最长可能递增子序列中右边部分为(ai, a_i1, a_i2, ..., a_ik)得到的长度,其中(a_i1, a_i2, ..., a_ik)表示输入序列右边部分m个 元素构成的最长递增子序列,该序列前面一个元素即为ai。ai可以为输入序列左边部分共N-m个元素中的任何一个。
假设输入序列的右边部分m个 元素的排列顺序为:a_j1, a_j2, ..., a_jm,那么 f(ai,m)关联的最长递增子序列中可能包含第一个元素a_j1,也可能不包含它。
a) 如果包含 a_j1,那么f(ai,m)可以表示成:
0或者1 + f(a_j1,m-1)
其中 a_j1 > ai时,二者构成递增关系,这时取值 1 + f(a_j1,m-1);如果 a_j1 <= ai时,二者不构成递增关系,这时取值0。
b) 如果不包含 a_j1,那么f(ai,m)可以表示成:
f(ai,m-1)
要使 f(ai,m)符合最优解,显然得到递推关系
f(ai,m) = max( 1 + f(a_j1,m-1), f(ai,m-1) ) .....式1
这里 1 + f(a_j1,m-1)部分 要求 a_j1 > ai。
第二种情况:如果 最长递增子序列中前面N-m个元素都未出现,同样考虑: 用g(m)表示最长可能递增子序列中右边部分为(a_i1, a_i2, ..., a_ik)得到的长度。假设输入序列的右边部分m个 元素的排列顺序为:a_j1, a_j2, ..., a_jm,那么g( m)关联的最长递增子序列中可能包含第一个元素a_j1,也可能不包含它。
a) 如果包含 a_j1,那么g(m)可以表示成: f( a_j1,m-1)
b) 如果不包含 a_j1,那么g(m)可以表示成:g(m-1)
同样取二者的最大值得到:
g(m) = max( f( a_j1,m-1), g(m-1) ) .....式2
初始值g(0)=0,因为 输入序列 右 边没有任何元素时最大长度为0;
以上两种情况中 初始值 f(ai,0)=1,ai为输入序列中任何一个元素,因为该元素本身构成最长 递增子序列。
例如输入序列为(1,7,3,5,9,4,8),那么当m=2时,右边的序列为(4,8),前面共有5个元素(1,7,3,5,9),所以可得到6个递推关系式:
f(1,2) = max( 1 + f(4,1), f(1,1) ) 因为1<4
f(7,2) = max( 0, f(7,1) ) 因为7>=4
f(3,2) = max( 1 + f(4,1), f(3,1) ) 因为3<4
f(5,2) = max( 0, f(5,1) ) 因为5>=4
f(9,2) = max( 0, f(9,1) ) 因为9>=4
g(2) = max(f(4,1), g(1))
每次需要计算的值的个数为N-m+1 (m=1,2,..., N),具体实现时只需存储相邻两轮循环的值,即前一轮循环得到 N-m个值,本轮循环得到 N-m+1个值。在以下的实现中,用数组F[]表示 前一轮循环得到的值,用 数组 currF[] 表示 本 轮循环得到的值, g(m)的值作为数组的最后一个元素存放。时间复杂度为O(n^2),空间复杂度为O(n)。
解法(针对ZOJ)
import java.util.Scanner;public class Main { public static int solve(int A[]) { int N = A.length; int[] F = new int[N + 1]; for (int i = 0; i < N; i++) { F[i] = 1; } F[N] = 0; for (int rightCount = 1; rightCount <= N; rightCount++) { int head = A[N - rightCount]; int leftCount = N - rightCount; int[] currF = new int[N + 1 - rightCount]; int headPrevVal = F[N - rightCount]; for (int i = 0; i < leftCount; i++) { int includeHeadCase = 0; if (A[i] < head) { includeHeadCase = 1 + headPrevVal; } int excludeHeadCase = F[i]; int optimalVal = (includeHeadCase < excludeHeadCase) ? excludeHeadCase : includeHeadCase; currF[i] = optimalVal; } int prevSpecialVal = F[F.length - 1]; int specialOptimal = headPrevVal < prevSpecialVal ? prevSpecialVal : headPrevVal; currF[currF.length - 1] = specialOptimal; F = currF; } return F[F.length - 1]; } public static void main_test(String[] args) { int[] A = { 1, 7, 3, 5, 9, 4, 8 }; int result = Main.solve(A); System.out.println(result); } public static void main(String[] args) { Scanner cin = new Scanner(System.in); String line = cin.nextLine(); line = line.trim(); int testCasesNum = Integer.parseInt(line); int[][] testCases = new int[testCasesNum][]; // blank line cin.nextLine(); for (int i = 0; i < testCasesNum; i++) { line = cin.nextLine(); line = line.trim(); int size = Integer.parseInt(line); // read the sequence line = cin.nextLine(); line = line.trim(); int[] seq = new int[size]; String[] tokens = line.split(" "); for (int j = 0; j < tokens.length; j++) { String token = tokens[j].trim(); if (token.length() > 0 && j < size) { seq[j] = Integer.parseInt(token); } } testCases[i] = seq; // blank line if (i < testCasesNum - 1) cin.nextLine(); } for (int i = 0; i < testCasesNum; i++) { int[] seq = testCases[i]; int result = Main.solve(seq); System.out.println(result); if (i < testCasesNum - 1) { System.out.println(); } } }}
解法(针对POJ): 和针对ZOJ的解法一样,只是输入和输出不太一样(ZOJ可以输入多个test cases,POJ只输入一个test case)。下面是main(...)方法的写法:
public static void main(String[] args) { Scanner cin = new Scanner(System.in); String line = cin.nextLine(); line = line.trim(); int size = Integer.parseInt(line); // read the sequence line = cin.nextLine(); line = line.trim(); int[] seq = new int[size]; String[] tokens = line.split(" "); for (int j = 0; j < tokens.length; j++) { String token = tokens[j].trim(); if (token.length() > 0 && j < size) { seq[j] = Integer.parseInt(token); } } int result = Main.solve(seq); System.out.println(result); }