動態規劃——線性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問題,希望大家好好理解這個問題,因為他真的狠重要!
今天的分享就到這裡,我們下次見。