LIS最长上升子序列

题意:有一个长为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 }

 

posted @ 2019-08-26 21:58  romaLzhih  阅读(210)  评论(0编辑  收藏  举报