题意:有一个长为n的数列,求出这个序列中最长的上升子序列长度(不连续,不能等于)。
解法1:简单dp(n2)
思路:
状态设计:F [ i ] 代表以 A [ i ] 结尾的 LIS 的长度
状态转移:F [ i ] = max { F [ j ] + 1 ,F [ i ] } (1 <= j < i,A[ j ] < A[ i ])
边界处理:F [ i ] = 1 (1 <= i <= n)
1 const int maxn = 103, INF = 0x7f7f7f7f; 2 int a[maxn], f[maxn]; 3 int n,ans = -INF; 4 int main() 5 { 6 scanf("%d", &n); 7 for(int i=1; i<=n; i++) 8 { 9 scanf("%d", &a[i]); 10 f[i] = 1; 11 } 12 for(int i=1; i<=n; i++) 13 for(int j=1; j<i; j++) 14 if(a[j] < a[i]) 15 f[i] = max(f[i], f[j]+1); 16 for(int i=1; i<=n; i++) 17 ans = max(ans, f[i]); 18 printf("%d\n", ans); 19 return 0; 20 }
解法2:贪心优化(nlogn)
思路:新建一个 low 数组,low [ i ]表示长度为i的LIS结尾元素的最小值。
注意这里的最小值需要更新,而且因为长度为3的LIS结尾元素最小值(low[3])必定大于长度为2的LIS结尾元素最小值(low[4]),所以这个序列就是递增的。
当面对一个数字arr[i],若其大于low[i]数组最后一个值,则增加数组长度并且将其填到最后面。否则更新low[i],更新的时候我们由于单调递增可以二分找位置。
1 /* ********************************************** */ 2 int k = 1; 3 dp[k] = arr[1]; 4 for(int i = 2; i <= n; i++){ 5 if(dp[k] < arr[i]) dp[++k] = arr[i]; //如果比最后一个元素大,那么就添加再最后末尾处 6 else *(lower_bound(dp + 1, dp + 1 + k, arr[i])) = arr[i]; //如果比最后一个元素小,那么就替换该序列第一个比他大的数; 7 } 8 /* ********************************************** */
POJ3616
题意:有M个时间段,给你每个时间段的开始和结束时间,还有这个时间段的产量,每个时间段不能重合,且这个时间段用完之后在一定时间内不能再生产,问你怎样选择可以使得最后产量最大。
解法:带权的LIS,长度相当于时间段的产量,时间段相当于个数,dp跑即可
1 struct COW { 2 int l, r, len; 3 bool operator<(COW &rhs) const { 4 return l == rhs.l ? r < rhs.r : l < rhs.l; 5 } 6 } cow[MAXN]; 7 int N, M, R, ans = -1; 8 int f[MAXN]; 9 int main() { 10 // freopen("input.txt", "r", stdin); 11 scanf("%d %d %d", &N, &M, &R); 12 rep(i, 1, M) scanf("%d %d %d", &cow[i].l, &cow[i].r, &cow[i].len); 13 sort(cow + 1, cow + 1 + M); 14 rep(i, 1, M) { 15 f[i] = cow[i].len; 16 rep(j, 1, i - 1) if (cow[j].r + R <= cow[i].l) { 17 f[i] = max(f[i], f[j] + cow[i].len); 18 } 19 } 20 rep(i, 1, M) ans = max(ans, f[i]); 21 printf("%d", ans); 22 return 0; 23 }
POJ1065
题意:有N个木棒,起点和终点分别为 stick[i].l 和 stick[i].r ,要将这些木棒排成不下降的序列,问最少能排成几个这样的序列。
如:( 9 , 4 ) , ( 2 , 5 ) , ( 1 , 2 ) , ( 5 , 3 ) , 和 ( 4 , 1 ) 这5根木棒,那么最少的方案是:( 4 , 1 ) , ( 5 , 3 ) , ( 9 , 4 ) , ( 1 , 2 ) , ( 2 , 5 )。 共需要两根
解法:通过鸽巢原理将题目转换为LIS问题。
为了便于理解,首先我们将所有的木棒按照起点的降序排列,我们设此基础下终点 r 的 LIS 长度为 L,我们先将LIS中包含的木棒都挖出来,形成“空穴”。
接下来假设问题的答案是将这一堆木棒分成 x 份,假如 x < L,那么根据鸽巢原理一定存在某堆木棒会有大于等于两个空穴。此时任选LIS中的两个元素放入,他们满足 stick[i].r <= stick[i+1].r 为升序,但是当前的木棒是按照起点降序的排列,此时出现了矛盾,所以木棒分成的分数一定等于木棒终点 r 的 LIS 长度。
代码里面反着写的,意思一样。
1 int T, N; 2 struct STICK { 3 int l, r; 4 bool operator<(const STICK& rhs) const { 5 return l == rhs.l ? r <= rhs.r : l < rhs.l; 6 } 7 } stick[MAXN]; 8 int dp[MAXN]; 9 10 int main() { 11 // freopen("input.txt", "r", stdin); 12 scanf("%d", &T); 13 while (T--) { 14 CLR(dp); 15 scanf("%d", &N); 16 for (int i = 1; i <= N; i++) scanf("%d%d", &stick[i].l, &stick[i].r); 17 sort(stick + 1, stick + N + 1); 18 for (int i = 1; i <= N; i++) { 19 dp[i] = 1; 20 for (int j = 1; j < i; j++) 21 if (stick[j].r > stick[i].r) { 22 dp[i] = max(dp[i], dp[j] + 1); 23 } 24 } 25 int ans = -1; 26 for (int i = 1; i <= N; i++) ans = max(ans, dp[i]); 27 printf("%d\n", ans); 28 } 29 return 0; 30 }