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

Zombie’s Treasure Chest

题目链接

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

题意

两种物品无穷多个,第一种物品重量s1s_1,价值v1v_1,第二种物品重量s2s_2,价值v2v_2,揹包重nn,求能装的最大价值之和. 数据全都是2e92e9.也就是两种物品的完全揹包.

题解

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

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

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

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

尝试一

直接套板子,设xx为第一种物品取的个数,显然x[0,ns1]x \in [0,\lfloor \frac{n}{s_1} \rfloor],那么第二种物品的个数就是y=ns1v1s2y =\lfloor \frac{n-s_1*v_1}{s_2} \rfloor.

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

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

尝试结果

多次尝试以后,一直WA,自己造了组极端数据2000000000,2,3,1,12000000000,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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章