锯木棒问题 Oj问题
写在前面:成功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)); } }