1. 程式人生 > 實用技巧 >試題 演算法提高 天天向上(dfs、dp)

試題 演算法提高 天天向上(dfs、dp)

問題描述   A同學的學習成績十分不穩定,於是老師對他說:“只要你連續4天成績有進步,那我就獎勵給你一朵小紅花。”可是這對於A同學太困難了。於是,老師對他放寬了要求:“只要你有4天成績是遞增的,我就獎勵你一朵小紅花。”即只要對於第i、j、k、l四天,滿足i<j<k<l並且對於成績wi<wj<wk<wl,那麼就可以得到一朵小紅花的獎勵。現讓你求出,A同學可以得到多少朵小紅花。 輸入格式   第一行一個整數n,表示總共有n天。第二行n個數,表示每天的成績wi。 輸出格式   一個數,表示總共可以得到多少朵小紅花。 樣例輸入
6
1 3 2 3 4 5 樣例輸出 6 資料規模和約定   對於40%的資料,n<=50;
  對於100%的資料,n<=2000,0<=wi<=1e9。 思路 1.記憶化搜尋 維護一個二維陣列dp,pos代表當前位置,cnt代表是作為遞增的第cnt天。dp[pos][cnt]存的是當前位置作為遞增的第cnt天到cnt=4所能得到的方案數。 累加除倒數3個數的其餘數作為遞增序列的第一天的方案數即是答案。
#include<bits/stdc++.h>
using namespace std;
typedef long long int ll;
ll a[2005];
ll dp[
2005][5]; ll n,p,t; ll dfs(int pos,int cnt){ if(cnt==4)return 1;///達到4個遞增數字,返回1 if(dp[pos][cnt]!=-1)return dp[pos][cnt];///記憶化 ll c=0;///當前位置的遞增天數到能滿足遞增4天的方案數,注意是區域性變數 for(int i=pos+1;i<n;i++){ if(a[i]>a[pos]){ c+=dfs(i,cnt+1); } } return dp[pos][cnt]=c;///
記憶一波返回 } int main(){ cin>>n; memset(dp,-1,sizeof(dp)); for(int i=0;i<n;i++)cin>>a[i]; ll ans=0; for(int i=0;i<n-3;i++){ ans+=dfs(i,1);///累加每個數作為遞增序列的第一個的方案數,後3個除外 } cout<<ans<<endl; return 0; }
2.動態規劃 這裡的dp[i][k]這裡代表從i天開始遞增k個數的方案數。 預設遞增1個數自己本身為1個,我們理解dp[i][k]等於 數值比a[i]大的且位置比i後的j, 且遞增的個數為k-1的方案數累加得到。每次把所有j位置的所得到的不同的遞增數的方案轉移到i上。 從後往前遞推,狀態轉移方程dp[i][j]= ∑dp[k][j-1] (k>i,a[k]>a[i]),轉移過程中當方案數不存在時退出。
#include<bits/stdc++.h>
using namespace std;
typedef long long int ll;
ll a[2005];
ll dp[2005][5];
ll n,p,t;
int main(){
    cin>>n;
    for(int i=0;i<n;i++)cin>>a[i];
    for(int i=n-1;i>=0;i--){
        dp[i][1]=1;///第一個遞增數為自己
        for(int j=i+1;j<n;j++){
            if(a[i]<a[j]){///如果後面的數比i大
                int k=2;///從使遞增數為2開始
                while(k<=4){
                    if(dp[j][k-1]==0)break;
                    dp[i][k]+=dp[j][k-1];
                    k++;
                }
            }
        }
    }
    ll ans=0;
    for(int i=0;i<n-3;i++){
        ans+=dp[i][4];
    }
    cout<<ans<<endl;
    return 0;
}