书中的状态设计一开始不太能理解。为什么换就要全都换?为什么枚举 j 让前 j 个灯泡选取子问题的最优解,而 j+1 到 i 全部换成 i?不可以隔着换吗?
对于第一个问题,起步时可以进行简单的分类讨论来探寻规律:
1、如果灯泡价格比之前贵,那么我们换灯泡本身是亏钱的。只有当全部灯泡都换完,减掉一个电源的时候,我们才盈利。
2、如果灯泡价格比之前还便宜,那么我们为什么不接着享受这个待遇呢?全部换完还能获得电源加成。
3、如果灯泡价格前后一样,那么全部换完获取电源加成是划算的。
因此灯泡要么不换(第一种情况:最后减掉一个电源也亏),要么全换
因此换灯泡就变成了整体换型号的操作。
对于第二个问题,可以用简单的代数求证:
假设存在连续的XYZ,X换Z划算,X换Y不划算,Y换Z不划算。(即产生了隔着换的情况)。注意如果X换Y划算,Y换Z也划算,那么显然X换Z也划算。如果X换Y划算,X换Z、Y换Z都不划算,这种情况则属于求子问题的最优解。
那么:
X换Z划算 => X.k + X.l * (Z.c - X.c) > 0
X换Y不划算 => X.k + X.l * (Y.c - X.c) < 0
因为数据都是正整数,综合两式可得 Z.c > Y.c
Y换Z是否划算 => Y.k + Y.l * (Z.c - Y.c) ?> 0
因为Z.c > Y.c,所以上式恒大于0,因此Y换Z一定也是划算的。所以最优解永远不会包含“隔着换”的情况。
这样算法的正确性就可以基本确定了。如果不是有书中的提示,估计自己很难独立做出来。
记忆化搜索比递推法慢很多,个人认为主要因为memset和递归函数调用。
递推:Run Time: 0.143
#define UVa "LT9-6.11400.cpp" //Lighting System Design
char fileIn[30] = UVa, fileOut[30] = UVa;
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
struct Bulb {
int v, k, c, l;
Bulb(){v = k = c = l = 0;}
Bulb(int* a){
v = a[0]; k = a[1]; c = a[2]; l = a[3];
}
bool operator < (const Bulb& b) const {
return v < b.v;
}
};
//Global Variables. Reset upon Each Case!
const int maxn = 1000 + 10;
int n;
Bulb bulbs[maxn];
int d[maxn];
int s[maxn];
/
int main() {
while(scanf("%d", &n) && n) {
for(int i = 0; i < n; i ++) scanf("%d%d%d%d", &bulbs[i].v, &bulbs[i].k, &bulbs[i].c, &bulbs[i].l);
sort(bulbs, bulbs + n);
int cnt = 0;
for(int i = 0; i < n; i ++) {
cnt += bulbs[i].l;
s[i] = cnt;
}
for(int i = 0; i < n; i ++) {
d[i] = s[i]*bulbs[i].c + bulbs[i].k;
for(int j = 0; j < i; j ++)
d[i] = min(d[i], d[j] + (s[i] - s[j])*bulbs[i].c + bulbs[i].k);
}
printf("%d\n", d[n-1]);
}
return 0;
}
记忆化搜索:0.362s
#define UVa "LT9-6.11400.cpp" //Lighting System Design
char fileIn[30] = UVa, fileOut[30] = UVa;
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
struct Bulb {
int v, k, c, l;
Bulb(){v = k = c = l = 0;}
Bulb(int* a){
v = a[0]; k = a[1]; c = a[2]; l = a[3];
}
bool operator < (const Bulb& b) const {
return v < b.v;
}
};
//Global Variables. Reset upon Each Case!
const int maxn = 1000 + 10;
int n;
Bulb bulbs[maxn];
int d[maxn];
int s[maxn];
/
int dp(int i) {
if(d[i]) return d[i];
d[i] = s[i]*bulbs[i].c + bulbs[i].k;
for(int j = 0; j < i; j ++)
d[i] = min(d[i], dp(j) + (s[i] - s[j])*bulbs[i].c + bulbs[i].k);
return d[i];
}
int main() {
while(scanf("%d", &n) && n) {
for(int i = 0; i < n; i ++) scanf("%d%d%d%d", &bulbs[i].v, &bulbs[i].k, &bulbs[i].c, &bulbs[i].l);
sort(bulbs, bulbs + n);
memset(d, 0, sizeof(d));
int cnt = 0;
for(int i = 0; i < n; i ++) {
cnt += bulbs[i].l;
s[i] = cnt;
}
printf("%d\n", dp(n-1));
}
return 0;
}