Zombie’s Treasure Chest
題目鏈接
https://cn.vjudge.net/problem/UVA-12325
題意
兩種物品無窮多個,第一種物品重量,價值,第二種物品重量,價值,揹包重,求能裝的最大價值之和. 數據全都是.也就是兩種物品的完全揹包.
題解
不可思議吧,這題還能模擬退火?
但仔細一想,求解最優值,而且隨機解的生成也很簡單,當然可以嗎,模擬退火搞啦.
模擬退火的板子可以到我以前的博客裏找到,現在默認大家都知道模擬退火怎麼寫了.
這道題我雖然用模擬退火掉了,但也嘗試了好多發,現在把我採坑的過程根大家分享一下.
嘗試一
直接套板子,設爲第一種物品取的個數,顯然,那麼第二種物品的個數就是.
因此模擬退火的時候我可以在區間中隨機一個數作爲,然後計算,並且計算能量值.
最後調調參數,使得平衡一下答案精度和時間複雜度.
嘗試結果
多次嘗試以後,一直WA,自己造了組極端數據,發現根本過不去,總結原因:當隨機區間過大的時候,很難隨機到正確解,所以算法就在某個半山腰停住了.
嘗試二
要想能想要枚舉到最優解,區間一定不能太大.我們可以分塊進行模擬退火,這樣可以保證每次隨機的區間不會太大,區間上的某一個點被隨機到的概率就更大了,這種做法我還沒有試過,但是感覺應該可行.我們進一步發現,這個函數的峯不會太多(實際沒幾個)大致是具有單調性質的,因此我們採用二分區間的做法,即對於當前區間,用模擬退火算出一個最優解,然後用這個解與區間中點做比較從而確定下一個需要進行模擬退火的區間.
通過多次調參之後:
代碼
#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;
}