1. 程式人生 > >演算法:C++實現動態規劃中的幾個典型問題

演算法:C++實現動態規劃中的幾個典型問題

動態規劃的思想在程式設計中佔有相當的分量,動態規劃的主要思想就是把大問題劃分為小問題,通過求解小問題來逐漸解決大問題。
滿足動態規劃思想的問題具備兩個典型特徵:
最優子結構:就是說區域性的最優解能夠決定全域性的最優解,最優解的子問題也是最優的。
子問題重疊 :就是說大問題劃分為小問題時,並不是每次都是新問題,有的小問題可能已經在前面的計算中出現過。
下面介紹幾個程式設計人員筆試常遇到的動態規劃問題。

  • 連續子陣列的最大和問題
描述:一維模式識別中,常常需要計算連續子向量的最大和,當向量全為正數的時候,問題很好解決。但是,如果向量中包含負數,是否應該包含某個負數,並期望旁邊的正數會彌補它呢?例如:{6,-3,-2,7,-15,1,2,2}
,連續子向量的最大和為8(從第0個開始,到第3個為止)。
int maxSum(std::vector<int> array){
    if(array.empty()){
        return 0;
    }
    if(array.size()==1){
        return array[0];
    }
    int sumCurrent=array[0];
    int result=array[0];
    for(int i=1;i<array.size();++i){
        sumCurrent=std::max(sumCurrent+array
[i],array[i]); result=std::max(result,sumCurrent); } return result; }
  • 環形公路加油站
描述:有一個環路,中間有N個加油站,加油站裡面的油是g1,g2...gn,加油站之間的距離是d1,d2...dn,問其中是否能找到一個加油站,使汽車從這個加油站出發,走完全程。如果存在滿足條件的加油站,返回該加油站的序號,否則返回-1
int select(const std::vector<int> g,const std::vector<int> d){
    std
::vector<int> ex_g(g.size()<<2); for(int i=0;i<ex_g.size();++i){ //初始化 ex_g[i]=g[i%ex_g.size()]-d[i%ex_g.size()]; } //找出n個連續字首都大於0的序列 int start=ex_g[0]; int oil=ex_g[0]; for(int i=1;i<ex_g.size();++i){ if(oil<0){ start=i; oil=ex_g[i]; } else{ oil+=ex_g[i]; if(i-start>=g.size()){ return start; } } } return -1; }
  • 查詢暗黑樹
    描述:一個只包含’A’、’B’和’C’的字串,如果存在某一段長度為3的連續子串中恰好’A’、’B’和’C’各有一個,那麼這個字串就是純淨的,否則這個字串就是暗黑的。例如:BAACAACCBAAA 連續子串”CBA”中包含了’A’,’B’,’C’各一個,所以是純淨的字串。AABBCCAABB 不存在一個長度為3的連續子串包含’A’,’B’,’C’,所以是暗黑的字串。你的任務就是計算出長度為n的字串(只包含’A’、’B’和’C’),有多少個是暗黑的字串。
#include<iostream>
#include<vector>
long blackNum(int num){
    std::vector<long> vec(num+1,0);
    vec[1]=3;
    vec[2]=9;
    for(int i=3;i<=num;i++){
        vec[i]=2*vec[i-1]+vec[i-2];
    }
    return vec[num];
}
int main(){
    int num;
    std::cin>>num;
    std::cout<<blackNum(num)<<std::endl;
    return 0;
}
  • 袋鼠過河
描述:一隻袋鼠要從河這邊跳到河對岸,河很寬,但是河中間打了很多樁子,每隔一米就有一個,每個樁子上都有一個彈簧,袋鼠跳到彈簧上就可以跳的更遠。每個彈簧力量不同,用一個數字代表它的力量,如果彈簧力量為5,就代表袋鼠下一跳最多能夠跳5米,如果為0,就會陷進去無法繼續跳躍。河流一共N米寬,袋鼠初始位置就在第一個彈簧上面,要跳到最後一個彈簧之後就算過河了,給定每個彈簧的力量,求袋鼠最少需要多少跳能夠到達對岸。如果無法到達輸出-1
輸入分兩行,第一行是陣列長度N (1N10000),第二行是每一項的值,用空格分隔。
輸出最少的跳數,無法到達輸出-1
#include<iostream>
#include<vector>
#include<algorithm>

int main(){
    int N;
    std::cin>>N;
    std::vector<int> vec(N,0);
    for(int i=0;i<N;++i){
        std::cin>>vec[i];
    }
    int hops=-1;
    std::vector<int> dp(N,10000);//輔助vector陣列
    dp[0]=1;//第一跳
    for(int i=0;i<N;i++){//遍歷所有情況
        for(int j=1;j<=vec[i];j++){//對於vec[i]值,下一跳可以是1-vec[i]的任意值
            if(i+j>=N){//符合過橋條件,分兩種情況處理
                if(hops==-1){
                    hops=dp[i]==10000?-1:dp[i];
                }
                else{
                    hops=std::min(hops,dp[i]);
                }
            }
            //不符合過橋條件,更新跳到當前位置的最小跳數
            else{
                dp[i+j]=std::min(dp[i+j],dp[i]+1);
            }
        }
    }
    std::cout<<hops<<std::endl;
    return 0;
}
  • 合唱團問題
描述:有 n 個學生站成一排,每個學生有一個能力值,牛牛想從這 n 個學生中按照順序選取 k 名學生,要求相鄰兩個學生的位置編號的差不超過 d,使得這 k 個學生的能力值的乘積最大,你能返回最大的乘積嗎?
輸入:每個輸入包含 1 個測試用例。每個測試資料的第一行包含一個整數 n (1<=n<= 50),表示學生的個數,接下來的一行,包含 n 個整數,按順序表示每個學生的能力值 ai(-50 <= ai <= 50)。接下來的一行包含兩個整數,k 和 d (1 <= k <= 10, 1 <= d <= 50)。
輸出:輸出一行表示最大的乘積。

題目分析

#include<iostream>
#include<vector>
#include<algorithm>
int main(){
    int n;
    std::cin>>n;
    std::vector<int> students(n);
    for(int i=0;i<n;++i){
        std::cin>>students[i];
    }
    int k,d;
    std::cin>>k>>d;
    //fm[k][i]記錄取到K個值且最後一個數的下標為i時,取到的數最大
    std::vector<std::vector<long long>> fm(k+1,std::vector<long long>(n,0));
    //fm[k][i]記錄取到K個值且最後一個數的下標為i時,取到的數最小
    std::vector<std::vector<long long>> fn(k+1,std::vector<long long>(n,0));
    long long res=0;
    for(int i=0;i<n;++i){//遍歷所有元素
        for(int j=1;j<=k;++j){//保證取到的值不超過k個
            if(j==1){
                fm[j][i]=students[i];
                fn[j][i]=students[i];
                continue;
            }
            //第i值前面的值可能有多種情況,只要間隔不超過d
            for(int m=1;m<=d;++m){
                if(i-m>=0&&i-m<n){//保證合理區間
                    //比較取最大
                    fm[j][i]=std::max(fm[j][i],std::max(fm[j-1][i-m]*students[i],fn[j-1][i-m]*students[i]));
                    //比較去最小
                    fn[j][i]=std::min(fn[j][i],std::min(fm[j-1][i-m]*students[i],fn[j-1][i-m]*students[i]));
                }
            }
        }
        res=std::max(res,fm[k][i]);//記錄每次取到k個數的最大乘積
    }
    std::cout<<res<<std::endl;
    return 0;
}