POJ 1631题目大意如下:
给定N个测试,每个测试的数据:第一行输入P,表示有P个连接,接下来的P个数据是按照下标从1到P排列的,每个下标i对应一个连接数a[i],表示i和a[i]是连接的,生动点就如下图所示,问题要求给出一个数字,表示的是在这P个连接数里,没有互相交叉的最大组合数。
从上图看的话,其实只是有助于理解题目意思,但是要解决问题的话,这个图还是会带来干扰。肯定不可以直接去模拟,所以我把测试的P个数据按照下标数排列出来,一下发现问题原来如此:就是一个最长上升子序列问题(LIS)。由于一边在学习《挑战程序设计竞赛》这本书,所以问题一下就得到解决的算法。
由于不能有信号线的交叉,那么比如之前的一个下标i对应了a[i],那么之后的下标j能够对应的连接值一定要比a[i]大,所以这么看来就是求一个序列的最大上升子序列。使用DP,但是定义DP数组的方式有两种,第一种肯定会超时,第二种通过,这里两种方法都写下来吧:
1.定义数组dp[i]:表示以a[i]结尾的最大上升子序列的长度。
既然是这样定义,那么每次对于一个新的dp[i],都要检查之前的a[j],如果出现a[j] < a[i]那么使用这个dp[j]去更新dp[i],表达式为:
dp[i] = max{dp[j] + 1| a[j] < a[i] & 1 <= j <= i}
以下是代码:
#include <iostream> #include <algorithm> #include <cstdio> using namespace std; const int maxn = 40000; int a[maxn + 1]; int N, P; int dp[maxn + 1]; void solve() { int ans = 0; fill(dp, dp + 1 + P, 1); dp[1] = 1; for (int i = 1; i <= P; i++) { for (int j = 1; j < i; j++) { if (a[j] < a[i]) dp[i] = max(dp[i], dp[j] + 1); } ans = max(ans, dp[i]); } printf("%d\n", ans); } int main() { scanf("%d", &N); while (N--) { scanf("%d", &P); for (int i = 1; i <= P; i++) scanf("%d", &a[i]); solve(); } return 0; }
为什么这样定义?从之前定义的角度来看,如果这个上升序列的末尾元素值越小,那么对于之后的求解则更加有利。实际使用的话,可以通过更新一个有序的dp就好,最后找到对后一个不是INF的dp下标就代表了最长长度,代码如下:
#include <iostream> #include <cstdio> #include <algorithm> using namespace std; const int maxn = 40000; const int INF = 100000; int a[maxn + 1]; int N, P; int dp[maxn + 1]; void solve() { fill(dp, dp + P, INF); for (int i = 0; i < P; i++) *lower_bound(dp, dp + P, a[i]) = a[i]; printf("%ld\n", lower_bound(dp, dp + P, INF) - dp); } int main() { scanf("%d", &N); while (N--) { scanf("%d", &P); for (int i = 0; i < P; i++) scanf("%d", &a[i]); solve(); } return 0; }