Uva12325 Zombie's Treasure Chest [二分区间+模拟退火]

Zombie’s Treasure Chest

题目链接

https://cn.vjudge.net/problem/UVA-12325

题意

两种物品无穷多个,第一种物品重量 s 1 s_1 s1,价值 v 1 v_1 v1,第二种物品重量 s 2 s_2 s2,价值 v 2 v_2 v2,背包重 n n n,求能装的最大价值之和. 数据全都是 2 e 9 2e9 2e9.也就是两种物品的完全背包.

题解

不可思议吧,这题还能模拟退火?

但仔细一想,求解最优值,而且随机解的生成也很简单,当然可以吗,模拟退火搞啦.

模拟退火的板子可以到我以前的博客里找到,现在默认大家都知道模拟退火怎么写了.

这道题我虽然用模拟退火 A A A掉了,但也尝试了好多发,现在把我采坑的过程根大家分享一下.

尝试一

直接套板子,设 x x x为第一种物品取的个数,显然 x ∈ [ 0 , ⌊ n s 1 ⌋ ] x \in [0,\lfloor \frac{n}{s_1} \rfloor] x[0,s1n],那么第二种物品的个数就是 y = ⌊ n − s 1 ∗ v 1 s 2 ⌋ y =\lfloor \frac{n-s_1*v_1}{s_2} \rfloor y=s2ns1v1.

因此模拟退火的时候我可以在区间 [ 0 , ⌊ n s 1 ⌋ ] [0,\lfloor \frac{n}{s_1} \rfloor] [0,s1n]中随机一个数作为 x x x,然后计算 y y y,并且计算能量值 E = x ∗ v 1 + y ∗ v 2 E = x*v_1+y*v_2 E=xv1+yv2.

最后调调参数,使得平衡一下答案精度和时间复杂度.

尝试结果

多次尝试以后,一直WA,自己造了组极端数据 2000000000 , 2 , 3 , 1 , 1 2000000000,2,3,1,1 2000000000,2,3,1,1,发现根本过不去,总结原因:当随机区间过大的时候,很难随机到正确解,所以算法就在某个半山腰停住了.

尝试二

要想能想要枚举到最优解,区间一定不能太大.我们可以分块进行模拟退火,这样可以保证每次随机的区间不会太大,区间上的某一个点被随机到的概率就更大了,这种做法我还没有试过,但是感觉应该可行.我们进一步发现,这个函数的峰不会太多(实际没几个)大致是具有单调性质的,因此我们采用二分区间的做法,即对于当前区间,用模拟退火算出一个最优解,然后用这个解与区间中点做比较从而确定下一个需要进行模拟退火的区间.

通过多次调参之后:

代码

#include <iostream>
#include <algorithm>
#include <cmath>
#include <ctime>
#include <cstring>
int T,cas;
long long n,s1,v1,s2,v2;
double randfloat() {
	return rand()/(RAND_MAX+0.0);
}
void solve() {
	std::cin >> n >> s1 >> v1 >> s2 >> v2;
	long long ansE = 0,ansx = 0;
	long long nowE = 0,x = 0;
	long long low = 0,up = n/s1;
	for(int cc = 1;cc <= 20;++cc) {
		double T0 = 1000000,Tk = 1,T = T0,d = 0.999;
		while(T > Tk) {
			long long newx = low + rand()%(up-low+1);
			long long newE = newx * v1 + ((n-newx*s1)/s2)*v2;
			if(newE < nowE  || randfloat() > exp((nowE-newE)/T)) {
				nowE = newE;
				x = newx;
			}
			T *= d;
			if(newE > ansE) {
				ansE = newE;
				ansx = newx;
			}
		}
		long long mid = (low + up)/2;
		if(ansx > mid) low = mid;
		else up = mid;
	}
	std::cout << "Case #" << ++cas << ": " << ansE << std::endl;
}
int main() {

	std::ios::sync_with_stdio(false);
	std::cin >> T;
	while(T--) solve();
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值