poj 1260(动态规划)

本文探讨了一个关于珍珠购买的算法问题,旨在找到购买一系列不同等级珍珠的最经济方案。通过对比两种算法思路,一种是错误的贪心算法实现,另一种则是正确的动态规划方法,并给出具体的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题描述:

已知:珍珠有(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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值