蘇蘇醬陪你學動態規劃(二)——合唱團
1、問題重述
有 n 個學生站成一排,每個學生有一個能力值,牛牛想從這 n 個學生中按照順序選取 k 名學生,要求相鄰兩個學生的位置編號的差不超過 d,使得這 k 個學生的能力值的乘積最大,你能返回最大的乘積嗎?
2、題目分析
題目要求n各學生中選擇k個,使這k個學生的能力值乘積最大。這是一個最優化的問題。另外,在優化過程中,提出了相鄰兩個學生的位置編號差不超過d的約束。
如果不用遞迴或者動態規劃,問題很難入手,並且,限制條件d也需要對每一個進行約束,程式設計十分複雜
所以,解決的方法是採用動態規劃(理由:1.求解的是最優化問題;2.可以分解為最優子結構)
首先,對該問題的分解是關鍵。
從n個學生中,選擇k個,可以看成是:先從n個學生裡選擇最後1個,然後在剩下的裡選擇k-1個,並且讓這1個和前k-1個滿足約束條件
其次,數學描述
為了能夠程式設計實現,需要歸納出其遞推公式,而在寫遞推公式之前,首先又需要對其進行數學描述
記第k個人的位置為one,則可以用f[one][k]表示從n個人中選擇k個的方案。然後,它的子問題,需要從one前面的left個人裡面,選擇k-1個,這裡left表示k-1個人中最後一個(即第k-1個)人的位置,因此,子問題可以表示成f[left][k-1].
學生能力陣列記為arr[n+1],第i個學生的能力值為arr[i]
one表示最後一個人,其取值範圍為[1,n];
left表示第k-1個人所處的位置,需要和第k個人的位置差不超過d,因此
max{k-1,one-d}<=left<=one-1
在n和k定了之後,需要求解出n個學生選擇k個能力值乘積的最大值。因為能力值有正有負,所以
當one對應的學生能力值為正時,
f[one][k] = max{f[left][k-1]arr[i]}(min{k-1,one-d}<=left<=one-1);
當one對應的學生能力值為負時
f[one][k] = max{g[left][k-1]arr[i]}(min{k-1,one-d}<=left<=one-1);
此處g[][]是儲存n個選k個能力值乘積的最小值陣列
3、程式設計實現
import java.util.Scanner; public class Main { public static void main(String[] args){ Scanner sc = new Scanner(System.in); while(sc.hasNext()) { //總人數 int n = sc.nextInt(); //學生能力值陣列,第i個人直接對應arr[i] int[] arr = new int[n + 1]; //初始化 for (int i = 1; i <= n; i++) {//人直接對應座標 arr[i] = sc.nextInt(); } //選擇的學生數 int kk = sc.nextInt(); //間距 int dd = sc.nextInt(); /** * 遞推的時候,以f[one][k]的形式表示 * 其中:one表示最後一個人的位置,k為包括這個人,一共有k個人 * 原問題和子問題的關係:f[one][k]=max{f[left][k-1]*arr[one],g[left][k-1]*arr[one]} */ //規劃陣列 long[][] f = new long[n + 1][kk + 1];//人直接對應座標,n和kk都要+1 long[][] g = new long[n + 1][kk + 1]; //初始化k=1的情況 for(int one = 1;one<=n;one++){ f[one][1] = arr[one]; g[one][1] = arr[one]; } //自底向上遞推 for(int k=2;k<=kk;k++){ for(int one = k;one<=n;one++){ //求解當one和k定的時候,最大的分割點 long tempmax = Long.MIN_VALUE; long tempmin = Long.MAX_VALUE; for(int left = Math.max(k-1,one-dd);left<=one-1;left++){ if(tempmax<Math.max(f[left][k-1]*arr[one],g[left][k-1]*arr[one])){ tempmax=Math.max(f[left][k-1]*arr[one],g[left][k-1]*arr[one]); } if(tempmin>Math.min(f[left][k-1]*arr[one],g[left][k-1]*arr[one])){ tempmin=Math.min(f[left][k-1]*arr[one],g[left][k-1]*arr[one]); } } f[one][k] = tempmax; g[one][k] = tempmin; } } //n選k最大的需要從最後一個最大的位置選 long result = Long.MIN_VALUE; for(int one = kk;one<=n;one++){ if(result<f[one][kk]){ result = f[one][kk]; } } System.out.println(result); } } }
本篇博文整理自牛客網菜鳥華的分享。