左老師的愛
Description:
左老師有n個題目,他希望出一張考試試卷,從中選取一定數量的題目,在不改變給定題目順序的情況下,要求選取的題目難度嚴格遞增,爲了防止有人AK,左老師希望在考試中出儘可能多的題目,求最大題目數量。
Input:
每個測試點只有一組測試數據。
第一行一個整數n表示題目數量,第二行n個整數ai表示題目難度。
測試點
Output:
輸出一個整數ans,一場考試中題目數量的最大值
Sample Input:
5
1 2 3 1 5
Sample Output:
4
Hint:
可以選取第1、2、3、5題
解題分析:
分析問題後可以發現,這是一個求最長上升子序列的問題。
一、簡單一般的動態規劃法:
#include <iostream>
using namespace std;
int arr[100005];
int lis(int arr[],int n)
{
int longest[n];
for (int i=0; i<n; i++)
longest[i] = 1;
for (int j=1; j<n; j++) {
for (int i=0; i<j; i++) {
if (arr[j]>arr[i] && longest[j]<longest[i]+1){ //注意longest[j]<longest[i]+1這個條件,不能省略。
longest[j] = longest[i] + 1; //計算以arr[j]結尾的序列的最長遞增子序列長度
}
}
}
int max = 0;
for (int j=0; j<n; j++) {
//cout << "longest[" << j << "]=" << longest[j] << endl;
if (longest[j] > max) max = longest[j]; //從longest[j]中找出最大值
}
return max;
}
int main()
{
int n;
while(scanf("%d",&n)!=EOF)
{
for(int i=0;i<n;i++)
{
scanf("%d",&arr[i]);
}
int ret = lis(arr,n);
cout << ret << endl;
}
return 0;
}
顯然這是O(n^2)的算法,但是當n到1e5的時候,會超時,需要優化。
二、最長上升子序列nlogn算法
來源於學長:https://blog.csdn.net/shuangde800/article/details/7474903
和博主:https://blog.csdn.net/yopilipala/article/details/60468359
#include<stdio.h>
#include<algorithm>
#include<cstring>
using namespace std;
int main()
{
int n,num[100100],lis[100100],len;
while(scanf("%d",&n)!=EOF)
{
len = 0;
memset(lis,0,sizeof(lis));
for(int i=0;i<n;i++)//如果i是從1開始,在lower_bound中的到的位置會返回到0,這樣就不可以把lis[1]的位置替換掉,從而WA。
{
scanf("%d",&num[i]);
}
lis[0] = num[0];
for(int i=1;i<n;i++)
{
if(num[i] > lis[len])//如果num比lis[len]選擇的終點大,則可以放入lis,即新的終點。
lis[++len] = num[i];
else
{
int pos = lower_bound(lis,lis+len,num[i]) - lis;//注意lower_bound 的用法,lower_bound返回的是一個地址
lis[pos] = num[i];//!!!
}
}
printf("%d\n",len+1);//len是從0開始的,所以要加上1。
}
}
這個算法的分析:
自己手動跟着算法跑一遍會比較清楚,這裏想提出我對這個算法的發現。
1.lis[i] (i從0開始)數組裏其實存儲的是,長度爲i+1的、最靠後的、最長上升子序列的最末的值,也就是以lis[i]結尾。
2.以序列2 1 5 3 6 4 8 9 7爲例
最終lis[]數組爲1 3 4 7 9。
當掃描到9,還沒掃描到7時,lis[]數組爲1 3 4 8 9。9在lis[4]這個位置上,繼續處理到7,其實如果7這個位置的數比9大,lis裏應該添在9之後,並且最長序列的長度應該爲5。但是7比9小,這個時候就要用二分搜索,搜索lis數組,位置前移去替代lis裏的數,每往前一個表示以7結尾的長度並沒有那麼長。
upper_bound()與lower_bound()使用方法:
二分搜索函數,頭文件爲<algorithm>
,要求查找的序列應爲有序序列。
是模版函數,使用範圍很廣,算法題中主要用於數組的二分查找。
參數:開始地址、結束地址、比較的元素
數組返回值:
upper_bound返回第一個大於的元素的下標;
lower_bound返回第一個大於等於元素的下標;
#include <iostream>
#include <algorithm>//必須包含的頭文件
using namespace std;
int main()
{
int point[10] = {1,3,7,7,9};
int a = upper_bound(point, point+5, 7) - point;//按從大到小搜索,7最後能插入數組point的哪個位置
int b = lower_bound(point, point+5, 7) - point;////按從小到大搜索,7最早能插入數組point的哪個位置
cout<<a<<endl;
cout<<b<<endl;
return 0;
}
output:
4
2
memset()函數用法:
#include<cstring>
#include<iostream>
using namespace std;
int main()
{
int lis[5];
memset(lis,0,sizeof(lis));
memset(lis,-1,sizeof(lis));
int a,b;
a=sizeof(lis)*5;
cout<<a<<endl;
b=sizeof(lis);
cout<<b<<endl;
for(int i=0;i<5;i++)
{
cout<<lis[i]<<endl;
}
return 0;
}
結果:
第一個參數是開始地址,第二個參數是設置爲0或-1(只有這兩個值),第三是字節大小
第三個參數應該是:sizeof(lis),而不是sizeof(lis)*5