NYOJ 17 單調遞增最長子序列(經典dp)


地址:http://acm.nyist.net/JudgeOnline/problem.php?pid=17

題目分析:同NYOJ 79 攔截導彈 

先解釋下什么叫子序列。若a序列刪去其中若干個元素后與b序列完全相同,則稱b是a的子序列。

我們假定存在一個單調序列{An}(以遞增序列為例),現在在其后面添加一個元素a(n+1),有兩種情況:

1.a(n+1)>a(n)   。此時,a(n+1)可以添加到An序列的尾部,形成一個新的單調序列,並且此序列長度大於之前An的長度;

2.a(n+1)<=a(n)。此時,a(n+1)當然不可以添加到An序列的尾部。

經過分析,我們可以得出這樣的結論:一個單調序列與其后面元素的關系僅與此序列的末尾元素有關

如此,便有了此題如下的dp解法:

建立一個一維數組dp[ ],用dp[i]保存長度為i的單調子序列的末尾元素的值,用top保存單調子序列的最大長度。

初始top=1,dp[1]=a1;

然后,我們從前向后遍歷序列An(i : 2~n)。顯然,我們會遇到兩種情況:

1.a[i] > dp[top]。此時,我們把a[i]加入到長度為top的單調序列中,這時序列長度為top+1,top值也跟着更新,即dp[++top] = a[i];

2.a[i]<=dp[top]。此時,a[i]不能加入長度為top的單調序列,那它應該加入長度為多少的序列呢?

   做法是,從后向前遍歷dp[ ] (j: top-1~1),直到滿足條件 a[i] > dp[j],此時,類似於步驟1,我們可以把a[i]加入到長度為j的單調序列中,這時此序列長度為j+1,

   我們將dp[j+1]的值更新為a[i]。可是,為什么要更新它呢?

   因為a[i]一定小於dp[j+1]。為什么呢?如果a[i]不小於dp[j+1],我們找到的j就應該是j+1而不是j。那么,我們為什么要保留把dp[j+1]的最小值呢?

   因為對於相同長度的單調遞增序列來說,末尾元素的值越小,其后元素加入此序列的可能性越大,也就是說,我們這樣做,是為了防止丟失最優解。

 

思路1:

      這題其實是跟導彈攔截一樣的,因為還有個加強版,所以把這個跟加強版一起貼上來。經典動態規划題,以后的動態規划很多都是從這個衍生出來的

一 最長遞增子序列問題的描述

  設L=<a1,a2,…,an>是n個不同的實數的序列,L的遞增子序列是這樣一個子序列Lin=<aK1,ak2,…,akm>,其中k1<k2<…<km且aK1<ak2<…<akm。求最大的m值。

     假設L是字符串長度,i是字符串下標,f(i)代表 當前以S[i]為首字母的字符串最大長度, 遞推公式:f(i)=max(f(i+1),f(i+2),...,f(L-1),f(L))+1;用一個int dp[i]數組保存當前的f(i)值,可想而知最后 *max_element(dp,dp+L) 便得到了答案。

二 算法:動態規划法:O(n^2)

  設f(i)表示L中以ai為末元素的最長遞增子序列的長度。則有如下的遞推方程:

     f(i)=max(f(i+1),f(i+2),...,f(L-1),f(L))+1;用一個int dp[i]數組保存當前的f(i)值,可想而知最后 *max_element(dp,dp+L) 便得到了答案

     這個遞推方程的意思是,在求以ai為末元素的最長遞增子序列時,找到所有序號在L前面且小於ai的元素aj,即j<i且aj<ai。如果這樣的元素存在,那么對所有aj,都有一個以aj為末元素的最長遞增子序列的長度f(j),把其中最大的f(j)選出來,那么f(i)就等於最大的f(j)加上1,即以ai為末元素的最長遞增子序列,等於以使f(j)最大的那個aj為末元素的遞增子序列最末再加上ai;如果這樣的元素不存在,那么ai自身構成一個長度為1的以ai為末元素的遞增子序列。一般在解決問題的時候都是用到動態規划,所以就貼出代碼了。主要用這個。。。。。。

代碼如下:

 1 #include<stdio.h>//**O(n^2)
 2 #include<string.h>
 3 int main()
 4 {
 5     char str[10001];
 6     int s,len,i,j,dp[10001],max;
 7     scanf("%d",&s);
 8     while(s--)
 9     {
10         max=0;
11         scanf("%s",str);
12         len=strlen(str);
13         for(i=0;i<=len-1;i++)
14         {
15             dp[i]=1;//**dp[i]的最小值為1**//
16         }
17         for(i=len-2;i>=0;i--)     //這點表示不太懂,正研究着。。。
18         {
19             for(j=i+1;j<=len-1;j++)
20             {
21                 if(str[i]<str[j]&&dp[i]<dp[j]+1) //最長遞增子序列則a[j]>a[i],而最長遞減子序列則a[j]<a[i]...好好體會。。。
22                 {
23                     dp[i]=dp[j]+1;//**更新dp[i]的值**//
24                 }
25             }
26         }
27         for(i=0;i<=len-1;i++)
28         {
29             if(dp[i]>max)
30             {
31                 max=dp[i];
32             }
33         }
34         printf("%d\n",max);
35     }
36 }                

 

思路2:

LIS算法(最長上升子序列)

LIS(Longest Increasing Subsequence)最長上升(不下降)子序列,有兩種算法復雜度為O(n*logn)和O(n^2)。在上述算法中,若使用朴素的順序查找在D1..Dlen查找,由於共有O(n)個元素需要計算,每次計算時的復雜度是O(n),則整個算法的時間復雜度為O(n^2),與原來算法相比沒有任何進步。但是由於D的特點(2),在D中查找時,可以使用二分查找高效地完成,則整個算法時間復雜度下降為O(nlogn),有了非常顯著的提高。需要注意的是,D在算法結束后記錄的並不是一個符合題意的最長上升子序列!算法還可以擴展到整個最長子序列系列問題。

有兩種算法復雜度為O(n*logn)和O(n^2)

借鑒代碼如下:

 1 //LIS算法實現
 2 #include <stdio.h>
 3 #include<string.h>
 4 char str[10001];
 5 int main()
 6 {
 7     int T,i,j;int len,ans;
 8     scanf("%d",&T);
 9     while(T--)
10     {
11         memset(str,0,sizeof(str));
12         scanf("%s%*c",str);
13         len=strlen(str);
14         ans=0;
15         for(i=0;i<len;i++)
16         {
17             for(j=0;j<ans;j++)
18                 if(str[j]>=str[i]) 
19                 { 
20                     str[j]=str[i]; 
21                     break;
22                 }
23             if(j==ans)
24             {
25                 str[j]=str[i];
26                 ans++;
27             }
28         }
29         printf("%d\n",ans);
30     }
31     return 0;
32 }  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM