1. 程式人生 > >動態規劃——線性DP.1

動態規劃——線性DP.1

動態規劃演算法通常用於求解具有某種最優性質的問題。

那它和貪心有區別嗎?

當然有。不然叫動態規劃幹啥?

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

小盆友:Dog&Peppa pig

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

然而,你是小盆友嗎?

如果是

 

如果不是,

 

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問題,希望大家好好理解這個問題,因為他真的狠重要!

 

今天的分享就到這裡,我們下次見。