動態規劃——線性DP.1

動態規劃算法一般用於求解具備某種最優性質的問題。算法

那它和貪心有區別嗎?數組

固然有。否則叫動態規劃幹啥?優化

幼兒園英語老師:DP是啥?spa

小盆友:Dog&Peppa pigcode

英語老斯:恩恩!真聰明!blog

然而,你是小盆友嗎?ci

若是是io

 

若是不是,class

 

DP是D****** P*******的縮寫。原理

意思是動態規劃。

聰明的bolt告訴你:是Dynamic Programming的縮寫!!!

 

動態規劃注重     表示狀態,轉移狀態

 so

講一個栗子:

LIS:

最長上升子序列

 

這是線性動態規劃中最經典的栗子之一。

最長上升子序列(Longest Increasing Subsequence,LIS),指一個序列中最長的單調遞增的子序列。

注意不是子串,因此能夠不相鄰。

好比說:

序列:3   2   1   4   6   8   7   9

它的LIS是5

3   4   6   8   9

或3   4   6   7   9

或2   4   6   8   9

······

還有不少種狀況。

因而咱們珂以得出:

動態規劃的最優解,有不一樣的組合狀況,但答案只有一個。

因此,若是NOIP出了動態規劃的題目時,通常會叫你求值,而不是求狀況。

 

這是好處!

BUT,有的老師不會好心,會給更多限制條件,使Ans只有一種狀況,那就更有難度了。

 

LIS問題要用動態規劃作。

方法一:

這是一個好理解的方法。

可是更好使耗時

 

 

不難看出,dp [ i ]就是第 i 個數的LIS

那代碼怎麼實現的呢?

先別急,咱們在舉個生活中的栗子。

 

老師要你算1+2+3+4+5+6+7+8+9=?時,你會算得45,

老師再問你1+2+3+4+5+6+7+8+9+10=?時,你是會用1+···+10,仍是用以前算的45+10?

聰明人會用後面一種。

 

因此,咱們根據這個方便的原理,發現我每次計算dp [ i ] 時,若是用到了前面的 dp 值,則會減小必定的計算量。

 

在咱們每次枚舉一個數的dp值時,只要掃描在它前面比它小的數,那些比他小的數的dp值的最大值+1就是所求數的dp值

由於比所求數小的數的dp值表示它的LIS,再來一個比它大的數,大樹數的LIS就等於小數的LIS+1.

但因爲小數的LIS有大有小,咱們又要求最長子序列,咱們就要取最大值。

 

一番思考後,咱們找到了狀態轉移方程,也就是動態規劃中最重要的東西:

對於每個 i ,咱們枚舉它前面的數 j,if (i > 它前面的數 j )   dp [ i ] = max ( dp [ i ] , dp [ j ] + 1 ) ;

 

這個算法的時間複雜度是O(n^2)的,慎用。

 

code:

 1 int n,a[1001]/*用來存序列*/,dp[1001]/*dp值*/;//數組大小根據題目而定。
 2 cin>>n;  3 dp[1]=1;                                   //1的dp值爲1
 4 for(int i=1;i<=n;i++)  5     cin>>a[i];  6 for(int i=1;i<=n;i++)  7 {  8     for(int j=1;j<i;j++)  9  { 10         if(a[i]>a[j]) 11  { 12             dp[i]=max(dp[i],dp[j]+1);      //狀態轉移
13  } 14  } 15 } 16 cout<<dp[n]<<endl;

 注意要初始化dp [ 1 ] = 1.剩下的爲 0.

還有另外一種時間複雜度爲 n log n 的LIS算法

 

看,栗子!

2   1   5   3   6   4   6   3 

在 dp 值相同的狀況下,保留較小的數顯然更好。由於後面的數若能跟較大的數構成上升子序列,也必定能能較小的數構成上升子序列,反之則不必定。例如 a_3=5 與 a_4=3 的 dp 均爲 2,但 a_6=4 不能與 a_3=5 構成上升子序列,而能夠和 a_4=3 構成上升子序列。 所以,不一樣的 dp 值只須要存一個對應的最小值,將這個最小值順序排列,他們必定是升序(嚴格來講是不降低)的。 因而,藉助二分查找的方式,就能夠很快查到更新的值,總體時間複雜度 O(nlogn)。

這個就是上面的一個優化,也沒有太多可講的,本身打一遍代碼也就熟了。

code:

 1 const int maxn=1e5+5;  2 int a[maxn];  3 int n;  4 int dp[maxn];  5 int ans=1;  6 int find(int x){  7     int l=1,r=ans,m;  8     while(l<r){  9         m=l+(r-l)/2; 10         if(dp[m]>=a[x]){ 11             r=m; 12  } 13         else{ 14             l=m+1; 15  } 16  } 17     return l; 18 }//二分查找 
19 int main(){ 20     scanf("%d",&n); 21     for(int i=1;i<=n;i++)scanf("%d",&a[i]); 22     dp[1]=a[1]; 23     for(int i=2;i<=n;i++){ 24         if(a[i]>dp[ans]){ 25             dp[++ans]=a[i]; 26  } 27         else{ 28             int pos=find(i); 29             dp[pos]=a[i]; 30  } 31  } 32     printf("%d",ans); 33     return 0; 34 }

這就是LIS問題,但願你們好好理解這個問題,由於他真的狠重要!

 

今天的分享就到這裏,咱們下次見。

相關文章
相關標籤/搜索