Diablo III ZOJ - 3769
题目链接 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。我觉得是这样。