石子合并動態(tài)規(guī)劃解決
在一個圓形操場的四周擺放著n堆石子。現(xiàn)要將石子有次序地合并成一堆。規(guī)定每次只能選擇相鄰的兩堆石子合并成新的一堆,并將新的一堆石子數(shù)記為該次合并的得分。試設計一個算法,計算出將n堆石子合并成一堆的最小得分和最大得分。
測試用例:
4(石子的堆數(shù))
4 4 5 9(每一堆的石子數(shù)目)
輸出: 43 54
分析:
首先,注意這是圓形的操場,石子堆圓形擺放,也就是最后一堆石子可以和第一堆石子合并,可以通過不同石子堆分別打頭排列,解決這個問題。
因為用的方法是動態(tài)規(guī)劃,所以肯定要為數(shù)組首先打好底,這樣后續(xù)操作才能在此基礎上進行。
此題與矩陣連乘問題相比,先計算每一堆石子自己合并的情況(這就是數(shù)組打底),然后一個循環(huán),r控制2堆,3堆,4堆.......n堆石子合并;每i堆石子合并的時候,需要控制起始位置,i代表從哪一堆開始合并,j代表到哪一堆合并結(jié)束。
這樣,i,j就控制了一個范圍,在這個范圍內(nèi)分成r堆合并。但究竟r在哪個地方分開,才能獲得最優(yōu)值,我們是不知道的,需要i,j范圍都遍歷看一看。
還有就是遞推公式,類比矩陣連乘問題理解,i到k的計算量+k到j的計算量+i到j的石子總數(shù),就是當前m[i][j]的值,代表i堆到j堆合并的最大(最小)得分。
#include<stdio.h>
#define n 4 // 4堆石子
int stone[n]={4,4,5,9};
int Max(){
int m[n][n]={0}; //m[i][j]是i堆到j堆合并的最大得分
int i,j,k,r,t;
int sum=0; //i堆到j堆的石子數(shù)
//每一堆自己合并
for(i=0;i<n;i++)
m[i][i]=0;
//兩堆以上合并
for(r=2;r<=n;r++){
for(i=0;i<=n-r;i++){
j=i+r-1;
sum=0;
for(k=i;k<=j;k++)
sum+=stone[k];
m[i][j]=m[i][i]+m[i+1][j]+sum;
for(k=i+1;k<j;k++){
t=m[i][k]+m[k+1][j]+sum;
if(t>m[i][j])
m[i][j]=t;
}
}
}
return m[0][n-1];
}
int Min(){
int m[n][n]={0}; //m[i][j]是i堆到j堆合并的最大得分
int i,j,k,r,t;
int sum=0; //i堆到j堆的石子數(shù)
//每一堆自己合并
for(i=0;i<n;i++)
m[0][0]=0;
//兩堆以上合并
for(r=2;r<=n;r++){
for(i=0;i<=n-r;i++){
j=i+r-1;
sum=0;
for(k=i;k<=j;k++)
sum+=stone[k];
m[i][j]=m[i][i]+m[i+1][j]+sum;
for(k=i+1;k<j;k++){
t=m[i][k]+m[k+1][j]+sum;
if(t<m[i][j])
m[i][j]=t;
}
}
}
return m[0][n-1];
}
int main(){
int max=0,min=10000;
int temp=0,tmax,tmin;
for(int i=0;i<n;i++){
temp=stone[0]; //因為是圓形的,不斷輪換,每個元素都打頭,
for(int j=0;j<n-1;j++){ //就實現(xiàn)了最后一堆石子和第一堆的合并
stone[j]=stone[j+1];
}
stone[n-1]=temp;
tmax=Max();
tmin=Min();
if(tmax>max)
max=tmax;
if(tmin<min)
min=tmin;
}
printf("最大:%d\n最小:%d\n",max,min);
return 0;
}
運行截圖