動態規劃基礎題:低價購買(最長下降子序列)

題目描述

“低價購買”這條建議是在奶牛股票市場取得成功的一半規則。要想被認爲是偉大的投資者,你必須遵循以下的問題建議:“低價購買;再低價購買”。每次你購買一支股票,你必須用低於你上次購買它的價格購買它。買的次數越多越好!你的目標是在遵循以上建議的前提下,求你最多能購買股票的次數。你將被給出一段時間內一支股票每天的出售價(2^16範圍內的正整數),你可以選擇在哪些天購買這支股票。每次購買都必須遵循“低價購買;再低價購買”的原則。寫一個程序計算最大購買次數。

這裏是某支股票的價格清單:

日期 1 2 3 4 5 6 7 8 9 10 11 12

價格 68 69 54 64 68 64 70 67 78 62 98 87

最優秀的投資者可以購買最多4次股票,可行方案中的一種是:

日期 2 5 6 10

價格 69 68 64 62

輸入輸出格式

輸入格式:
第1行: N (1 <= N <= 5000),股票發行天數

第2行: N個數,是每天的股票價格。

輸出格式:
輸出文件僅一行包含兩個數:最大購買次數和擁有最大購買次數的方案數(<=2^31)當二種方案“看起來一樣”時(就是說它們構成的價格隊列一樣的時候),這2種方案被認爲是相同的。


看完試題就應該能明白,這是一道典型的最長下降子序列問題。最長下降子序列是一個典型的O(n^2)動態規劃問題。本文將闡述如何求出最長下降子序列長度、統計個數以及快速去重。


長度求解
其實就是一個很簡單的轉移問題。對於第i個元素,它的最長下降子序列長度總是由前i-1個元素轉移得來的。具體而言,它的轉移總來源於前I-1個元素中滿足a[i]>a[j] (j∈[1,i-1])的最長下降子序列長度最大的元素。聽起來很繞口,我們用轉移方程來表示:
f[i]=f[j]+1 (j∈[1,i-1]且j∈N*, i∈[1,n], a[i]

#include <cstdio>
#define max(x,y) x>y?x:y
using namespace std;
int n,a[5005],f[5005];
int main()
{
    int i,j,ans=0;
    scanf("%d",&n);
    for(i=1;i<=n;i++) scanf("%d",&a[i]),f[i]=1;
    for(i=2,j=1;i<=n;j=j==i-1?1:j+1,i+=(j==1))
        if(a[i]<a[j]&&f[j]>=f[i]) f[i]=f[j]+1;
    for(i=1;i<=n;i++) ans=max(ans,f[i]);
    printf("%d",ans);
    return 0;
}

統計個數
如何進行個數統計?看起來這個問題很簡單,只要開一個數組,在轉移的時候不斷修改就行了。事實上,真正寫起來,要做的修改還是挺多的。

#include <cstdio>
#define max(x,y) x>y?x:y
using namespace std;
int n,a[5005],f[5005],c[5005];
int main()
{
    int i,j,ans=0;
    scanf("%d",&n);a[n+1]=-1;
    for(i=1;i<=n;i++) scanf("%d",&a[i]),f[i]=1,c[i]=1;
    for(i=2,j=1;i<=n+1;j=j==i-1?1:j+1,i+=(j==1)){
        if(a[i]<a[j]&&f[i]==f[j]+1) c[i]+=c[j];
        if(a[i]<a[j]&&f[j]>=f[i]) f[i]=f[j]+1,c[i]=c[j];
    }
    printf("%d %d\n",f[n+1]-1,c[n+1]);
    return 0;
}

輸入
4
4 4 2 1
輸出
3 2
哈!統計成功了……慢着,好像有哪裏不對……哪裏來的兩個序列?不都是4 4 2 1嗎?


快速去重
剛剛我們看到,會有重複的序列來影響計數,也就是題目描述的“看起來一樣”。因此,我們需要一種去重方法。
有人說:去重簡單!把所有的方案記錄下來,然後挨個比較不就好了?
行!只是……真的很慢。

那麼我們不妨換一種思維……由於重複只會影響計數數組c[i],並且重複現象的作用一定是產生在c[j]到c[i]的轉移過程中的。不如,我們從源頭出發,來阻止這個問題的發生。
在每一次進行計數後,開闢一次O(i)的校驗,當且僅當存在v[i]==v[j],f[i]==f[j]時,將c[i]強制設置爲0.

#include <cstdio>
#define max(x,y) x>y?x:y
using namespace std;
int n,a[5005],f[5005],c[5005];
int main()
{
    int i,j,ans=0;
    scanf("%d",&n);a[n+1]=-1;
    for(i=1;i<=n;i++) scanf("%d",&a[i]),f[i]=1,c[i]=1;
    for(i=2,j=1;i<=n+1;j=j==i-1?1:j+1,i+=(j==1)){
        if(a[i]<a[j]&&f[i]==f[j]+1) c[i]+=c[j];
        else if(a[j]==a[i]&&f[i]==f[j]) c[j]=0;
        if(a[i]<a[j]&&f[j]>=f[i]) f[i]=f[j]+1,c[i]=c[j];
    }
    printf("%d %d\n",f[n+1]-1,c[n+1]);
    return 0;
}
發佈了53 篇原創文章 · 獲贊 1 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章