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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章