1. 程式人生 > >【考題詳解】 4月DP練習賽題解

【考題詳解】 4月DP練習賽題解

跑步[Neal Wu, 2007]

奶牛們打算通過鍛鍊來培養自己的運動細胞,作為其中的一員,貝茜選擇的運動方式是每天進行N(1 <= N <= 10,000)分鐘的晨跑。在每分鐘的開始,貝茜會選擇下一分鐘是用來跑步還是休息。貝茜的體力限制了她跑步的距離。更具體地,如果貝茜選擇在第i分鐘內跑步,她可以在這一分鐘內跑D_i(1 <= D_i <= 1,000)米,並且她的疲勞度會增加1。不過,無論何時貝茜的疲勞度都不能超過M(1 <= M <= 500)。如果貝茜選擇休息,那麼她的疲勞度就會每分鐘減少1,但她必須休息到疲勞度恢復到0為止。在疲勞度為0時休息的話,疲勞度不會再變動。晨跑開始時,貝茜的疲勞度為0。還有,在N分鐘的鍛鍊結束時,貝茜的疲勞度也必須恢復到0,否則她將沒有足夠的精力來對付這一整天中剩下的事情。
請你計算一下,貝茜最多能跑多少米。
程式名: cowrun
【輸入格式】
第1行: 2個用空格隔開的整數:N 和 M
第2..N+1行: 第i+1為1個整數:D_i
【輸入樣例】 (cowrun.in)
5 2
5
3
4
2
10
【輸出格式】
第1行: 輸出1個整數,表示在滿足所有限制條件的情況下,貝茜能跑的最大距離
【輸出樣例】 (cowrun.out)
9
【輸出說明】
貝茜在第1分鐘內選擇跑步(跑了5米),在第2分鐘內休息,在第3分鐘內跑步(跑了4米),剩餘的時間都用來休息。因為在晨跑結束時貝茜的疲勞度必須為0,所以她不能在第5分鐘內選擇跑步。

這是一道動態規劃的題目,有兩個狀態:時間和疲勞值。因此我們就可以設F[i][j]為前i分鐘疲勞值為j的最大跑步距離。但是這道題目有一點特殊,因為有3種情況。
1.若當前休息且疲勞值為0,有可能是由上一秒疲勞值為0轉移過來的。
2.若當前跑完步後準備休息,那麼直接休息到底,即直接轉移到休息完的狀態
3.這一秒跑步且上一秒也跑步,直接轉移

基於上述三種情況,我們可以得到3個狀態轉移方程
1.

F [ i ] [ 0 ] = m a x ( F [ i
] [ 0 ] , F [ i 1 ] [ 0 ] )

2. F [ i + j ] [ 0 ] = m a x ( F [ i + j ] [ 0 ] , F [ i ] [ j ] ) ,表示由當前狀態直接休息到底
3. F [ i ] [ j ] = m a x ( F [ i ] [ j ] , F [ i 1 ] [ j 1 ] + a [ i ] ) ,其中a[i]表示跑步的時間,需要注意的是我們必須判斷上一個位置知否合法。

程式碼如下:

#include<bits/stdc++.h>
using namespace std;
int a[1000000];
int F[20000][1500];
inline int Max(int a,int b,int c){return max(a,max(b,c));}
int main()
{
    freopen("cowrun.in","r",stdin);
    freopen("cowrun.out","w",stdout);
    int n,m;
    cin>>n>>m;
    for (int i=1;i<=n;++i) cin>>a[i];
    for (int i=1;i<=n;++i)
    {
        F[i][0]=max(F[i-1][0],F[i][0]);//判斷上一部也是休息的情況 
        for (int j=1;j<=m;++j)
        {
            if (F[i-1][j-1]>0 || j==1) F[i][j]=max(F[i-1][j-1]+a[i],F[i][j]);//判斷上一步是運動的狀況 
            F[i+j][0]=max(F[i+j][0],F[i][j]);//直接修休息到下一步 
        }
    }
    cout<<F[n][0]<<endl;
    fclose(stdin);fclose(stdout);
    return 0;
}

渡河問題[Jeffrey Wang, 2007]

Farmer John以及他的N(1 <= N <= 2,500)頭奶牛打算過一條河,但他們所有的渡河工具,僅僅是一個木筏。
由於奶牛不會划船,在整個渡河過程中,FJ必須始終在木筏上。在這個基礎上,木筏上的奶牛數目每增加1,FJ把木筏劃到對岸就得花更多的時間。
當FJ一個人坐在木筏上,他把木筏劃到對岸需要M(1 <= M <= 1000)分鐘。當木筏搭載的奶牛數目從i-1增加到i時,FJ得多花M_i(1 <= M_i <= 1000)分鐘才能把木筏劃過河(也就是說,船上有1頭奶牛時,FJ得花M+M_1分鐘渡河;船上有2頭奶牛時,時間就變成M+M_1+M_2分鐘。後面的依此類推)。那麼,FJ最少要花多少時間,才能把所有奶牛帶到對岸呢?當然,這個時間得包括FJ一個人把木筏從對岸劃回來接下一批的奶牛的時間。
程式名: river
【輸入格式】
第1行: 2個用空格隔開的整數:N 和 M
第2..N+1行: 第i+1為1個整數:M_i
【輸入樣例】 (river.in)
5 10
3
4
6
100
1
【輸入說明】
FJ帶了5頭奶牛出門。如果是單獨把木筏劃過河,FJ需要花10分鐘,帶上1頭奶牛的話,是13分鐘,2頭奶牛是17分鐘,3頭是23分鐘,4頭是123分鐘,將5頭一次性載過去,花費的時間是124分鐘。
【輸出格式】
第1行: 輸出1個整數,為FJ把所有奶牛都載過河所需的最少時間
【輸出樣例】 (river.out)
50
【輸出說明】
Farmer John第一次帶3頭奶牛過河(23分鐘),然後一個人劃回來(10分鐘),最後帶剩下的2頭奶牛一起過河(17分鐘),總共花費的時間是23+10+17 = 50分鐘。

這可能是一道最基礎的線性且以為的動態規劃,類似於公交乘車的思想。即,設F[i]為前i頭牛能夠度過和的最小話費,那麼就列舉j,當前花費為前j頭牛的花費加上剩下幾頭牛的花費。那麼就可以寫出這道題目的狀態轉移方程:

F [ i ] = m i n ( F [ i ] , F [ j ] + c o s t [ i j ] ) cost[]類似於一個字首和,統計的是話費的總和。

程式碼如下:

#include<bits/stdc++.h>
using namespace std;
int a[1000000];
int F[20000][1500];
inline int Max(int a,int b,int c){return max(a,max(b,c));}
int main()
{
    freopen("cowrun.in","r",stdin);
    freopen("cowrun.out","w",stdout);
    int n,m;
    cin>>n>>m;
    for (int i=1;i<=n;++i) cin>>a[i];
    for (int i=1;i<=n;++i)
    {
        F[i][0]=max(F[i-1][0],F[i][0]);//判斷上一部也是休息的情況 
        for (int j=1;j<=m;++j)
        {
            if (F[i-1][j-1]>0 || j==1) F[i][j]=max(F[i-1][j-1]+a[i],F[i][j]);//判斷上一步是運動的狀況 
            F[i+j][0]=max(F[i+j][0],F[i][j]);//直接修休息到下一步 
        }
    }
    cout<<F[n][0]<<endl;
    fclose(stdin);fclose(stdout);
    return 0;
}

數字平方和

3.數字平方和
(numsquare.pas/c/cpp)
這是一個很無趣的數字遊戲,給你n個整數,再給一個目標和sum。你可以調整n個數中任意一個數的大小,但是要讓調整好的n個整數的平方和和sum相等。
比如有3個數 1 3 3。目標和是6,那麼我們可以把1變成2,把兩個3都變成1,這樣就完成調整。但是為了提高難度,我們規定假設某個數為A_i,調整為B_i,那麼要花費的代價是|A_i-B_i|*|A_i-B_i|。
那麼上面的演算法花費的代價就是9.當然肯定會有更小代價的演算法。
現在問題提出來,把n個數調整到位,最小花費的代價是多少?
如果存在無解的情況,輸出”-1”。
【輸入】
第一行兩個空格隔開的整數:n (1<=n<=10) and sum (1<=sum<=10,000)
接下來為n行,每行一個整數A_i(1<=A_i<=100)
【輸出】
一個整數,花費的最小代價

【輸入輸出樣例1】
——————
3 6
3
3
1
——————
5
——————

這道題其實難度較大。我們設F[i][j]為前i和數字累加和為j的最小話費。思維難度較大,但是我們可以去仔細地思考以下:每一個數字a[i],可以改變,同樣可以不改變,我們暫且考慮需要改變的情況。需要改變,我們便必然有這麼一個改變後的數字,設這個數字我j,改變的花費為cost[i][j],那麼我們可以去列舉這個總和,也就是F陣列的第二維狀態,則可以知道其中必然包含j*j,設k為這個綜合減去j*j的數字,則不能得出: F [ i ] [ k + j j ] = ( F [ i ] [ k + j j ] , F [ i 1 ] [ k ] + c o s t [ i ] [ j ] ) 其含義就是原來的數加上改變以後的數的最有值。

程式碼如下:

#include<bits/stdc++.h>
using namespace std;
int n,sum;
int a[200000];
int F[20][20000];
int cost[1000][1000];
int main()
{
    freopen("numsquare.in","r",stdin);
    freopen("numsquare.out","w",stdout);
    cin>>n>>sum;
    memset(F,100,sizeof(F));F[0][0]=0;
    for (int i=1;i<=n;i++) cin>>a[i];
    for (int i=1;i<=n;i++)
        for (int j=0;j<=200;j++)
            cost[i][j]=pow( abs(a[i]-j),2 );
    for (int i=1;i<=n;i++)//列舉每一個數字
        for (int j=0;j*j<=sum;j++)//列舉每一個小於sum且需要與a[i]替換的完全平方數 
            for (int k=sum-j*j;k>=0;k--)//列舉剩下的數字
                F[i][k+j*j]=min(F[i][k+j*j],F[i-1][k]+cost[i][j]);
    cout<<(F[n][sum]!=F[15][0]?F[n][sum]:-1);
    fclose(stdin);fclose(stdout);
    return 0;
}