问题描述:
已知:珍珠有(c1, c2, ..., cn)共n个品级,对应不同的价格(p1, p2, ..., pn)。购买品级为ci的珍珠需缴纳且只缴纳一次额外费用,具体值为10*pi。现购买一批珍珠,购买数量为(a1, a2, ..., an),ai代表购买品级为ci的珍珠的数量。整个数量结构可以变动,规则为:可购买需要品级的珍珠或同等数量更高品级的珍珠,但不能购买更低等级的珍珠
求:购买珍珠的最少花费
输入:第1行为case数;第2行为case 1的品级数;第2行至第n行为case1的各品级的ai,pi;第n+1行为case2的品级数……
输出:最少花费
Sample Input:
2
2
100 1
100 2
3
1 10
1 11
100 12
Sample Output:
330
1344
思路:
首先考虑了一种贪心算法:设e(i)为品级ci至最高品级cn的最少花费,则e(n)=[a(n)+10]*p(n),e(i)=min{[a(i}+10]*p(i), a(i)*p(j) j>i且品级cj已被购买过},原问题的解为e(0)
代码如下
#include <stdio.h>
#include <limits.h>
int a[110];
int p[110];
long e[110];
int f[110];
int case_n;
int class_n;
void dp(){
int n = class_n;
long e_min;
int has;
e[n-1] = (a[n-1]+10)*p[n-1];
f[n-1] = 1;
for(int i = n-2; i >= 0; i--){
f[i] = 0;
e_min = 100*1000*1000*10;
for(int j = i+1; j <= n-1; j++){
if(f[j] == 1 && a[i]*p[j] < e_min){
e_min = a[i]*p[j];
has = j;
}
}
if(e_min < (a[i]+10)*p[i]){
e[i] = e_min;
f[has] = 1;
}
else{
e[i] = (a[i]+10)*p[i];
f[i] = 1;
}
}
long sum = 0;
for(int i = 0; i < n; i++)
sum += e[i];
printf("%ld\n", sum);
}
int main(){
scanf("%d", &case_n);
for(int i = 0; i < case_n; i++){
scanf("%d", &class_n);
for(int j = 0; j < class_n; j++)
scanf("%d%d", &a[j], &p[j]);
dp();
}
return 0;
}
但这种思路是错误的,不能简单地将较低品级归于较高品级仅仅为了这次省了钱,还有可能更低的品级归于当前品级而总价格更低,也就是说,子问题贪心得到的最优解并不能保证最终原问题也是最优解
新的思路基于以下两个命题的正确性
- 如果品级为a的珍珠将与更高品级b合并,则必须将所有品级为a的珍珠与b合并。即不能将同品级的珍珠分到两个或更多的品级购买
- 等级为b的珍珠只能将比它低的若干个连续的等级合并进来,即品级为b的珍珠只能与品级b-1, b-2, ..., b-k的珍珠合并(反证法可证明)
设e(i)为从品级c1至ci购买的最少花费,则根据如上性质,可得递归表达式
即将(c1, c2, ..., ci)分割为(c1, c2, ..., cj)与(cj+1, ..., ci)两部分,前一部分为子问题的解,后一部分的所有品级均合并至ci,容易算出
算法枚举所有分割点cj,取最小值为e(i)
j=0代表一种特殊情况,即c1, c2, ..., ci-1均合并至ci,此时e(j)=0
代码:
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
int case_n;
int class_n;
int a[110];
int p[110];
long e[110];
long dp(){
int ee;
e[0] = (a[0]+10)*p[0];
for(int i = 1; i < class_n; i++){
e[i] = LONG_MAX;
for(int j = -1; j < i; j++){
ee = 0;
for(int k = j+1; k <= i; k++){
ee += a[k];
}
if(j == -1)
ee = (ee+10)*p[i];
else
ee = e[j]+(ee+10)*p[i];
e[i] = (ee < e[i]) ? ee : e[i];
}
}
return e[class_n-1];
}
int main(){
scanf("%d", &case_n);
for(int i = 0; i < case_n; i++){
scanf("%d", &class_n);
for(int j = 0; j < class_n; j++){
scanf("%d%d", &a[j], &p[j]);
}
printf("%ld\n", dp());
}
return 0;
}