“千丈之堤,以蝼蚁之穴溃。百尺之室,以突隙之烟焚。”——《韩非子》
题目
有一个 m x n
大小的矩形蛋糕,需要切成 1 x 1
的小块。
给你整数 m
,n
和两个数组:
horizontalCut
的大小为m - 1
,其中horizontalCut[i]
表示沿着水平线i
切蛋糕的开销。verticalCut
的大小为n - 1
,其中verticalCut[j]
表示沿着垂直线j
切蛋糕的开销。
一次操作中,你可以选择任意不是 1 x 1
大小的矩形蛋糕并执行以下操作之一:
- 沿着水平线
i
切开蛋糕,开销为horizontalCut[i]
。 - 沿着垂直线
j
切开蛋糕,开销为verticalCut[j]
。
每次操作后,这块蛋糕都被切成两个独立的小蛋糕。
每次操作的开销都为最开始对应切割线的开销,并且不会改变。
请你返回将蛋糕全部切成 1 x 1
的蛋糕块的 最小 总开销。
难度:困难
分析
此题为第406场周赛的第4题,周赛的第3题与此题相同,只是数据量小。我因为第一次参加周赛,之前从不做困难题,所以先做的第3题,使用的是动态规划。显然此题的数据量过大所以无法通过。
首先我进行如下设定:沿水平线切蛋糕会产生横块,沿垂直线切蛋糕会产生竖块,横块和竖块的数目只和各自方向上的刀数有关,所以水平切一刀会增加一横块,沿垂直切一刀会增加一竖块。(这很重要,一开始我想成切一刀数目翻倍了,结果后面就错了,导致我想复杂了,反复建立优先级公式都不对😶)
我们还需要明白一件事情:横刀下去一次切所有竖块,竖刀下去一次切所有横块。因为我们最终得到的是最优的下刀顺序,如果这刀不切完的话,就会留到后面切(最终切成1*1的小块),就会增加代价。有了以上结论,我们可以等同的理解为每条水平线下一次刀,每条竖直线下一次刀,因此两个数组中的每个数只用一次,但是横刀的代价需要乘上竖块的数目,竖刀的代价需要乘上横块的数目。
那么应该如何决定下刀的顺序?我们先考虑横刀和竖刀内部该如何抉择:显然代价大的刀应该放在前面切,因为每增加一横块(竖块),就需要多出一倍的代价,则初始代价越大,增加的代价也越大。现在考虑先切横刀还是先切竖刀:答案是先切代价大的刀。我这里无法给出数学证明,只能大致的理解为不管我们先切横刀还是竖刀,另一方的代价都会增加,即总有一方的代价会增加,那么我们先切代价大的,让代价小的增加。
经过上述的分析,思路就很清晰了:对数组进行排序后使用双指针,优先选大的数。
解答
class Solution {
public:
long long minimumCost(int m, int n, vector<int>& horizontalCut, vector<int>& verticalCut) {
sort(horizontalCut.begin(),horizontalCut.end(),greater<int>());
sort(verticalCut.begin(),verticalCut.end(),greater<int>());
long long ans=0;
int i=0,j=0; //横块和竖块的数目分别为i+1、j+1
while (i<m-1&&j<n-1){
if (horizontalCut[i]>=verticalCut[j]){
ans+=(j+1)*horizontalCut[i++];
}else{
ans+=(i+1)*verticalCut[j++];
}
}
while (i<m-1){
ans+=(j+1)*horizontalCut[i++];
}
while (j<n-1){
ans+=(i+1)*verticalCut[j++];
}
return ans;
}
};