寫在前面:成功AC一道題總是多舛啊
題目描述
/**
* 題目描述
* xiaok大佬最近再雇佣工人給他掰木棒。把一根長為L的木棒鋸成兩段,他需要支付給工人L元錢。xiaok大佬一開始只有長為L的一根木棒,他想把它鋸成n段,
* 每段長度分別為L1,L2,...,Ln,問xiaok大佬最少要付給工人多少錢?
*
* 輸入
* 第一行兩個整數n,L(1<n<103,n<L<109)
*
* 第二行n個整數L1,L2,...,Ln(0<Li<L,且保證L1+L2+...+Ln=L)
*
* 輸出
* 輸出一個整數,表示最小花費
* 樣例輸入
* 3 21
* 8 5 8
* 樣例輸出
* 34
*/
1. 經過思考,我判斷這題類似與動態規划里面的經典問題矩陣連乘相似,就是每次找一個最優的划分點,時間復雜度O(n**2),
代碼如下,這是錯誤答案!
public class Main { public static long getMinCost(int length, int seg, long [] segLens){ int[][] dp = new int[seg][seg]; for(int len = 2; len <= seg; ++len){ // 矩陣個數 for(int row = 1; row <= seg; ++row){ int col = row + len -1; if(col > seg)break; int minCost = Integer.MAX_VALUE; int sums = 0; for(int m = row; m <= col; ++m){ sums += segLens[m-1]; } for(int k = row; k < col; ++k){ minCost = Math.min(minCost, dp[row-1][k-1] + dp[k][col-1] + sums); } dp[row-1][col-1] = minCost; } } return dp[0][seg-1]; } }
2. 經過測試錯誤50%,好家伙,想了好久,最后問了同學,其實這個是有區別的,矩陣連乘划分的結果是有順序。比如說上面的21 按照矩陣連乘的最優划分是8,5,8,但是這道題不同啊,我只要把木棍鋸成三段不久可以了嗎,也可以是8,8,5啊,這個測試數據有點特殊,最后的結果是一樣的,也就是說按照矩陣連乘的結果不一定就是最小划分,可能還存在其他的順序無關的切分方法。
所以我的方法是貪心,每次都先把最長的那個段給切出來,下一次切分的時候代價就最小了。
以下代碼75%的通過率
public static long getMinCost(int length, int seg, long [] segLens){// 此題關鍵不能用矩陣連乘來做,還是自己思維不到位 // 矩陣連乘划分出來的具有一定的順序 // 8, 5, 8 這個是矩陣連乘的順序 // 8, 8, 5這個也滿足要求啊 // 這個題的思路就是貪心啦,每次都切最大的一段,保證剩下的可以最小 // 1. 升序排列,排序 O(nlgn) Arrays.sort(segLens); // 2. 倒序遍歷 // minCost是最小花費,seg是數組長度,segLens表示將要划分的長度 long minCost = 0; for(int i = seg - 1; i > 0; --i){ minCost += (long) length; length -= segLens[i]; } return minCost; // 75%正確率
}
3. 這肯定不行啊,還得優化,最后我采用自底向上的方法進行合並,用到了優先級隊列,不過寫的時候,我的有一種寫法還是75%通過率,我還是不清楚,最后修改了一種解法
public class Main { public static long getMinCost(int length, int seg, long [] segLens){
// 思路三
// 1. 每次取最小的兩個數合並
PriorityQueue<Long> priorityQueue = new PriorityQueue<>();
for(int i = 0; i < seg; ++i){
priorityQueue.add(segLens[i]);
}
long minCost = 0;
while (priorityQueue.size() > 1){
long sums = priorityQueue.remove() + priorityQueue.remove();
minCost += sums;
priorityQueue.add(sums);
// 錯誤代碼
// 這兩段的邏輯一樣的啊!
// minCost += priorityQueue.remove() + priorityQueue.remove();
//priorityQueue.add(minCost);
}
// return priorityQueue.remove()
return minCost;
}
4. 完整的ac代碼,這道題還是暴露了自己嚴重的思維缺陷,總是喜歡跳進自己的思維陷阱,基礎不扎實,還跳不出來,上課前還得預習下啊!!!
/** * @Author Fizz Pu * @Date 2020/10/18 下午5:02 * @Version 1.0 * 失之毫厘,繆之千里! */ import java.util.Arrays; import java.util.PriorityQueue; import java.util.Scanner; /** * 題目描述 * xiaok大佬最近再雇佣工人給他掰木棒。把一根長為L的木棒鋸成兩段,他需要支付給工人L元錢。xiaok大佬一開始只有長為L的一根木棒,他想把它鋸成n段, * 每段長度分別為L1,L2,...,Ln,問xiaok大佬最少要付給工人多少錢? * * 輸入 * 第一行兩個整數n,L(1<n<103,n<L<109) * * 第二行n個整數L1,L2,...,Ln(0<Li<L,且保證L1+L2+...+Ln=L) * * 輸出 * 輸出一個整數,表示最小花費 * 樣例輸入 * 3 21 * 8 5 8 * 樣例輸出 * 34 */ // 這個題有個關鍵點就是Li為整數,每段長度為整數 // 從i處划分,划分成i段和n-i段,那么就可以遞歸的解決問題 // 這還是一個最優解的問題,左邊如果出現最優解,右邊出現左右解,那么總結果就是最優的 // 當然會出現重復計算問題 // 這道題和矩陣連乘思路很像 // dp[i][j]表示表示把長度為i的木板分成j段 // dp[i][j] = dp[i][k] + dp[k][j] + sum(i:j) public class Main { public static long getMinCost(int length, int seg, long [] segLens){ // 思路三 // 1. 每次取最小的兩個數合並 PriorityQueue<Long> priorityQueue = new PriorityQueue<>(); for(int i = 0; i < seg; ++i){ priorityQueue.add(segLens[i]); } long minCost = 0; while (priorityQueue.size() > 1){ long sums = priorityQueue.remove() + priorityQueue.remove(); minCost += sums; priorityQueue.add(sums); } return minCost; } public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int seg = scanner.nextInt(), len = scanner.nextInt(); long[] segLens = new long[seg]; for(int i = 0; i < seg; ++i){ segLens[i] = scanner.nextLong(); } System.out.println(getMinCost(len, seg, segLens)); } }