有一根长度为L(L<1000)的棍子,还有n(n<50)个切割点的位置(按照从小到大排列)。你的任务是在这些切割点的位置处把棍子切成n+1部分,使得总切割费用最小。每次切割的费用等于被切割的木棍长度。例如,L=10,切割点为2, 4, 7。如果按照2, 4, 7的顺序,费用为10+8+6=24,如果按照4, 2, 7的顺序,费用为10+4+6=20。
【分析】
设d(i,j)为切割小木棍i~j的最优费用,则,边界条件为:if(i == j -1) d(i,j) = 0。其中最后一项a[j]-a[i]代表第一刀的费用。切完之后,小木棍变成i~k和k~j两部分,状态转移方程由此可得。把切割点编号为1~n,左边界编号为0,右边界编号为n+1,则答案为d(0,n+1)。状态有O(n2)个,每个状态的决策有O(n)个,时间复杂度为O(n3)。值得一提的是,本题可以用四边形不等式优化到O(n2),有兴趣的读者请参见本书的配套《算法竞赛入门经典——训练指南》或其他参考资料。
方法一:按照j-i递增的顺序递推
#include <iostream>
#include <cstring>
using namespace std;
const int N = 50+5, INF = 0x3f3f3f3f;
int point[N], d[N][N];
int main(int argc, char** argv) {
int len;
while(cin>> len && len){
int n;
cin>> n;
for(int i = 1; i <= n; i++) cin>> point[i];
point[0] = 0; point[n+1] = len;
for(int i = 0; i <= n; i++) d[i][i+1] = 0;//len==1时的边界。
for(int len = 2; len <= n+1; len++){
for(int i = 0, j= i+len; j <= n+1; i++, j++){
d[i][j] = INF;
for(int k = i+1; k < j; k++)
d[i][j] = min(d[i][j], d[i][k]+d[k][j]+point[j]-point[i]);
}
}
cout<<"The minimum cutting is "<< d[0][n+1]<< ".\n";
}
return 0;
}
方法二:记忆搜索
#include <iostream>
#include <cstring>
using namespace std;
const int N = 50+5, INF = 0x3f3f3f3f;
int point[N], d[N][N];
int dp(int i,int j){
// if(i == j-1) return 0;
if(d[i][j] != -1) return d[i][j];
int &ans = d[i][j] = INF;
for(int k = i+1; k < j; k++)
ans = min(ans, dp(i,k)+dp(k,j)+point[j]-point[i]);
return ans ;
}
int main(int argc, char** argv) {
int len;
while(cin>> len && len){
int n;
cin>> n;
for(int i = 1; i <= n; i++) cin>> point[i];
point[0] = 0; point[n+1] = len;
memset(d,-1,sizeof(d));
for(int i = 0; i <= n+1; i++) d[i][i+1] = 0;//直接在外设定边界,减少在dp中的判断。
cout<<"The minimum cutting is "<< dp(0,n+1)<< ".\n";
}
return 0;
}