動態規劃題解 D002 合唱團
題目解讀
原題連結: 牛客網線上程式設計題 2017年校招專題
題目描述
有 n 個學生站成一排,每個學生有一個能力值,牛牛想從這 n 個學生中按照順序選取 k 名學生,要求相鄰兩個學生的位置編號的差不超過 d,使得這 k 個學生的能力值的乘積最大,你能返回最大的乘積嗎?
輸入描述
每個輸入包含 1 個測試用例。每個測試資料的第一行包含一個整數 n (1 <= n <= 50),表示學生的個數,接下來的一行,包含 n 個整數,按順序表示每個學生的能力值 ai(-50 <= ai <= 50)。接下來的一行包含兩個整數,k 和 d (1 <= k <= 10, 1 <= d <= 50)
輸出描述
輸出一行表示最大的乘積
示例一
輸入
3
7 4 7
2 50
輸出
49
題意理解
題目的意思還是較為清晰的,即在一定的約束條件下尋找一個子序列,使得子序列的值乘積最大。這裡的約束條件有兩個:一個是子序列的個數是固定的為k;另一個是子序列相鄰兩個序號之間的差值有一個上限d。
演算法分析
拿到這樣一個題目一開始不太好下手,比如我現在取序列中的一個值出來,這個值位於子序列中的哪個部分呢?子序列中的下一個值應該是向前搜尋還是向後搜尋呢?這些都是比較困惑人的。但是仔細一想,並回憶動態規劃問題中常見的思路:我們可以人為地確定當前搜尋到的這個編號為子序列中的最後一個位置。這樣的話,我們可以設定一個DPB[index][k],這個陣列的含義即表示,以編號index的學生作為數量為k的子序列的最後一項對應的子序列最大乘積;
同樣地,可以設定DPS[index][k]表示對應子序列的最小乘積。
通過設計這樣的資料結構,我們其實將諸多變化量中的一些變數進行了固定,縮小了搜尋的範圍。
在設計出子狀態後,我們期望得到狀態轉移方程。在這裡,考慮到第二個約束條件,我們能夠搜尋的第k-1個子序列物件應當對應於原序列中編號為 [ index-d , index-1 ] 這樣區間內的。由於對於index編號的同學的價值可正可負,因此我們需要將它與前一個狀態最大或者最小的乘積同時相乘進行比較,才能得出當前編號條件下的最大和最小;由此,可以得到狀態轉移方程為:
DPB[index][k] = max(DPB[i][k-1]*value[index] , DPS[i][k-1]*value[index] ) 其中i的取值區間為 [ index-d , index-1 ]
DPS[index][k] = min(DPB[i][k-1]*value[index],DPS[i][k-1]*value[index] ) 其中i的取值區間為 [ index-d , index-1 ]
Part 1 初值的確定
由上面的分析可以知道,對應於DPB[index][1] = value[index] ; DPS[index][1] = value[index] ;
Part 2 狀態轉移方程的編寫
第一重迴圈用來確定子序列的長度,即從2迴圈到k;
第二重迴圈用來確定哪些編號可以進行子序列的搜尋,這是因為比如我現在將編號1作為一個個數為3的子序列的最後一項,這是顯然不合理的,因為它的長度根本不滿足條件。所以這個編號的範圍應該介於第一重迴圈的值到n
第三重迴圈用來確定能夠向前搜尋的長度,注意應該是max(index-d,1)作為初值的選擇
Part 3 最終結果的選取
我們應該從DPB[k][k]開始遍歷到DPB[n][k]為止,選取其中最大的值作為結果
兩個注意事項
事項1: 在計算的過程中,由於涉及int的乘法,故有可能出現溢位的情況,應該使用long作為變數型別
事項2: 在確定max(index-d,1)這個第三重迴圈時,注意在迴圈過程中可能會出現DBP[a][b]中a
程式碼
#include<stdio.h>
#include<iostream>
#include<string.h>
#define MAXNUM 51
using namespace std ;
int n,k,d;
long value[MAXNUM];
long DPB[MAXNUM][MAXNUM];
long DPS[MAXNUM][MAXNUM];
long max(long a,long b)
{
return (a>b?a:b);
}
long min(long a,long b)
{
return (a<b?a:b);
}
void readData()
{
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%ld",&value[i]);
}
scanf("%d%d",&k,&d);
return ;
}
void init()
{
for(int i=0;i<MAXNUM;i++){
for(int j=0;j<MAXNUM;j++){
DPS[i][j]=99999;
DPB[i][j]=-99999;
}
}
for(int i=1;i<=n;i++){
DPB[i][1] = value[i];
DPS[i][1] = value[i];
}
return ;
}
void Search()
{
long max_num = -9999;
//第一層迴圈用來挑選k個人
for(int i=2;i<=k;i++){
//第二層迴圈用來表示第幾個人對應於i編號
for(int j=i;j<=n;j++){
//第三層迴圈用來表示可以搜尋的前向範圍
for(int c=max(j-d,1);c<j;c++){
if(c<i-1){
continue;
}
DPB[j][i]=max(DPB[j][i],max(DPB[c][i-1]*value[j],DPS[c][i-1]*value[j]));
DPS[j][i]=min(DPS[j][i],min(DPS[c][i-1]*value[j],DPB[c][i-1]*value[j]));
}
}
}
for(int i=k;i<=n;i++){
if(max_num<DPB[i][k]){
max_num = DPB[i][k];
}
}
printf("%ld\n",max_num);
return ;
}
int main()
{
readData();
init();
Search();
return 0;
}