題目連結 https://vjudge.net/problem/ZOJ-3769
程式碼轉自:https://blog.csdn.net/Since_natural_ran/article/details/76037493?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171878667916800185844738%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=171878667916800185844738&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduend~default-2-76037493-null-null.142v100control&utm_term=zoj3769
分組揹包的變式。
題意:
給出13類裝備,每種裝備都有攻擊力和防禦值,每種裝備只允許選擇一個,13種裝備裡如果選擇了雙手裝備,就不能選擇武器和護盾了,還有戒指可以雙手各帶一個,求滿足防禦值至少在M的情況下,攻擊力最大為多少
思路:
首先我們先雜湊把13類裝備對應到vector[]中
void init()
{
mp["Weapon"] = 1;
mp["Shield"] = 2;
mp["Two-Handed"] = 3;
mp["Finger"] = 4;
mp["Feet"] = 5;
mp["Legs"] = 6;
mp["Waist"] = 7;
mp["Wrist"] = 8;
mp["Hand"] = 9;
mp["Torso"] = 10;
mp["Neck"] = 11;
mp["Shoulder"] = 12;
mp["Head"] = 13;
}
然後我們按照題意處理這些資料
int length1 = G[1].size();
int length2 = G[2].size();
for(int j = 0;j < length2; j++) {
G[3].push_back(G[2][j]);
}
for(int i = 0;i < length1; i++) {
G[3].push_back(G[1][i]);
for(int j = 0;j < length2; j++) {
G[3].push_back((goods){G[1][i].Damage+G[2][j].Damage,G[1][i].Toughness+G[2][j].Toughness});
}
}
int length3 = G[4].size();
for(int i = 0;i < length3; i++) {
for(int j = i + 1;j < length3; j++) {
G[4].push_back((goods){G[4][i].Damage+G[4][j].Damage,G[4][i].Toughness+G[4][j].Toughness});
}
}
處理好了之後,就是一個分組揹包。
不過直接寫分組揹包像我這樣會TLE
設$dp[i][j]$表示前i個物品,韌度為j的最大攻擊力,然後我算了一個韌度和,是1500000
int dp[15][1500000];
int ans = 0;
for (int i = 4; i <= 14; i++) {
for (int j = 1500000; j >= 0; j--) {
for (auto [a, b] : v[i]) {
if(j >= a) {
dp[i][j] = max(dp[i - 1][j - a] + b, dp[i][j]);
}
}
}
}
for (int i = M; i <= 1500000; i++) {
ans = max(ans, dp[14][i]);
}
if(ans == 0) {
cout << "-1\n";
} else {
cout << ans << "\n";
}
這樣寫就會mle+tle
我們來看看正解最佳化的多妙。
memset(dp,-1,sizeof(dp));
dp[2][0] = 0;
for(int i = 3;i <= 13; i++) {
for(int j = m;j >= 0; j--) {
dp[i][j] = max(dp[i][j],dp[i-1][j]); //更新裝備狀態,即使後邊沒有裝備了,也能到到之前的狀態。(必須步驟)
if(dp[i-1][j] == - 1) continue; //若要買下一個裝備,那麼上一個裝備的狀態一定要達到
int length = G[i].size();
for(int k = 0;k < length; k++) {
goods e = G[i][k];
int d = min(e.Toughness + j,m); //如果大於m則當做m即可(極妙)
dp[i][d] = max(dp[i][d],dp[i-1][j]+e.Damage);
}
}
}
cout<<dp[13][m]<<endl;
我覺得有兩個地方很妙:
- 他把第二維的上限設定成m,如果大於m等價於m,最佳化了空間,也最佳化了時間
- 第二個是,這個題與平常的分組揹包不同的是,分組揹包是本金為m,能獲得的最優價值是多少。這個題是隻要用m去獲得價值,所以題解是從前向後dp。我覺得是這樣。