传送门: https://codeforces.com/gym/102056/problem/I
题意
刚
开
始
,
有
初
始
值
都
为
0
的
A
(
伤
害
)
和
D
(
伤
害
增
量
)
。
刚开始,有初始值都为0的A(伤害)和D(伤害增量)。
刚开始,有初始值都为0的A(伤害)和D(伤害增量)。
D
的
含
义
是
,
每
回
合
刚
开
始
都
可
以
给
A
增
加
D
的
伤
害
。
D的含义是,每回合刚开始都可以给A增加D的伤害。
D的含义是,每回合刚开始都可以给A增加D的伤害。
有
n
个
回
合
,
每
个
回
合
有
3
个
值
a
,
b
,
c
。
有n个回合,每个回合有3个值a,b,c。
有n个回合,每个回合有3个值a,b,c。
然 后 三 个 操 作 : 然后三个操作: 然后三个操作:
- 操 作 一 : 可 以 攻 击 , 总 伤 害 增 加 A + a 。 操作一:可以攻击,总伤害增加A+a。 操作一:可以攻击,总伤害增加A+a。
- 操 作 二 : 给 增 量 D 增 加 b 。 操作二:给增量D增加b。 操作二:给增量D增加b。
- 操 作 三 : 给 伤 害 A 增 加 c 。 操作三:给伤害A增加c。 操作三:给伤害A增加c。
问 如 何 操 作 可 以 使 总 伤 害 值 最 大 。 n < = 100 。 问如何操作可以使总伤害值最大。n<=100。 问如何操作可以使总伤害值最大。n<=100。
思路
首 先 可 以 想 到 3 100 复 杂 度 的 暴 力 , 显 然 不 行 。 首先可以想到3^{100}复杂度的暴力,显然不行。 首先可以想到3100复杂度的暴力,显然不行。
然 后 正 向 遍 历 ? 显 然 也 不 行 , 因 为 你 前 面 的 操 作 对 后 续 会 有 影 响 , 所 以 考 虑 逆 向 。 然后正向遍历?显然也不行,因为你前面的操作对后续会有影响,所以考虑逆向。 然后正向遍历?显然也不行,因为你前面的操作对后续会有影响,所以考虑逆向。
那 该 怎 么 逆 向 呢 ? 如 果 我 们 知 道 我 们 攻 击 了 几 次 , 并 且 哪 几 次 攻 击 , 就 可 以 算 出 当 前 的 贡 献 。 那该怎么逆向呢?如果我们知道我们攻击了几次,并且哪几次攻击,就可以算出当前的贡献。 那该怎么逆向呢?如果我们知道我们攻击了几次,并且哪几次攻击,就可以算出当前的贡献。
比 如 n = 5 , 我 正 在 第 2 天 , 如 果 我 们 第 3 和 第 5 天 攻 击 了 , 那 么 当 前 有 三 个 操 作 : 比如n=5,我正在第2天,如果我们第3和第5天攻击了,那么当前有三个操作: 比如n=5,我正在第2天,如果我们第3和第5天攻击了,那么当前有三个操作:
- 执 行 操 作 一 : a n s + = A + a [ i ] 执行操作一:ans+=A+a[i] 执行操作一:ans+=A+a[i]
- 执 行 操 作 二 : a n s + = ( 3 − 2 ) ∗ b [ i ] + ( 5 − 2 ) ∗ b [ i ] 执行操作二:ans+=(3-2)*b[i]+(5-2)*b[i] 执行操作二:ans+=(3−2)∗b[i]+(5−2)∗b[i]
- 执 行 操 作 三 : a n s + = 2 ∗ b [ i ] 执行操作三:ans+=2*b[i] 执行操作三:ans+=2∗b[i]
很 显 然 , 这 些 我 们 可 以 O ( 1 ) 判 断 , 所 以 我 们 要 根 据 后 面 的 选 择 来 做 出 前 面 的 选 择 , 这 就 是 逆 向 d p 。 很显然,这些我们可以O(1)判断,所以我们要根据后面的选择来做出前面的选择,这就是逆向dp。 很显然,这些我们可以O(1)判断,所以我们要根据后面的选择来做出前面的选择,这就是逆向dp。
那 我 们 需 要 知 道 哪 些 状 态 呢 ? 第 几 轮 , 攻 击 次 数 ( 统 计 c [ i ] 贡 献 ) , 哪 几 次 攻 击 ( 统 计 b [ i ] 贡 献 ) 。 那我们需要知道哪些状态呢?第几轮,攻击次数(统计c[i]贡献),哪几次攻击(统计b[i]贡献)。 那我们需要知道哪些状态呢?第几轮,攻击次数(统计c[i]贡献),哪几次攻击(统计b[i]贡献)。
第
几
轮
?
最
大
为
100
,
i
。
第几轮?最大为100,i。
第几轮?最大为100,i。
攻
击
次
数
?
最
大
为
100
,
j
。
攻击次数?最大为100,j。
攻击次数?最大为100,j。
哪
几
次
攻
击
?
这
个
很
不
容
易
想
到
,
那
就
是
记
录
攻
击
次
数
下
标
和
,
k
。
哪几次攻击?这个很不容易想到,那就是记录攻击次数下标和,k。
哪几次攻击?这个很不容易想到,那就是记录攻击次数下标和,k。
那 么 就 得 到 d p [ i ] [ j ] [ k ] 表 示 第 i 轮 , 攻 击 了 j 次 , 并 且 下 标 和 为 k 的 最 大 伤 害 。 那么就得到dp[i][j][k]表示第i轮,攻击了j次,并且下标和为k的最大伤害。 那么就得到dp[i][j][k]表示第i轮,攻击了j次,并且下标和为k的最大伤害。
那 么 就 可 以 进 行 状 态 转 移 了 : 那么就可以进行状态转移了: 那么就可以进行状态转移了:
- 操 作 一 : d p [ i ] [ j + 1 ] [ k + i ] = m a x ( d p [ i ] [ j + 1 ] [ k + i ] , d p [ i + 1 ] [ j ] [ k ] + a [ i ] ) 操作一:dp[i][j+1][k+i]=max(dp[i][j+1][k+i], dp[i+1][j][k]+a[i]) 操作一:dp[i][j+1][k+i]=max(dp[i][j+1][k+i],dp[i+1][j][k]+a[i])
- 操 作 二 : d p [ i ] [ j ] [ k ] = m a x ( d p [ i ] [ j ] [ k ] , d p [ i + 1 ] [ j ] [ k ] + ( k − j ∗ i ) ∗ b [ i ] ) 操作二:dp[i][j][k]=max(dp[i][j][k],dp[i+1][j][k]+(k-j*i)*b[i]) 操作二:dp[i][j][k]=max(dp[i][j][k],dp[i+1][j][k]+(k−j∗i)∗b[i])
- 操 作 三 : d p [ i ] [ j ] [ k ] = m a x ( d p [ i ] [ j ] [ k ] , d p [ i + 1 ] [ j ] [ k ] + j ∗ c [ i ] ) 操作三:dp[i][j][k]=max(dp[i][j][k],dp[i+1][j][k]+j*c[i]) 操作三:dp[i][j][k]=max(dp[i][j][k],dp[i+1][j][k]+j∗c[i])
所 以 先 逆 序 遍 历 n , 在 遍 历 j , 最 后 遍 历 k 。 所以先逆序遍历n,在遍历j,最后遍历k。 所以先逆序遍历n,在遍历j,最后遍历k。
想 到 这 里 就 已 经 很 好 了 , 可 能 是 我 们 没 有 精 力 继 续 想 了 , 没 有 优 化 。 所 以 一 直 过 不 去 。 想到这里就已经很好了,可能是我们没有精力继续想了,没有优化。所以一直过不去。 想到这里就已经很好了,可能是我们没有精力继续想了,没有优化。所以一直过不去。
我 们 知 道 n 有 100 , 所 以 100 ∗ 100 ∗ ( 1 + 100 ) ∗ 100 / 2 会 M L E , 然 后 看 到 i 其 实 可 以 滚 动 的 。 我们知道n有100,所以100*100*(1+100)*100/2会MLE,然后看到i其实可以滚动的。 我们知道n有100,所以100∗100∗(1+100)∗100/2会MLE,然后看到i其实可以滚动的。
所 以 把 前 面 都 改 成 i % 2 , 后 面 都 改 成 ( i + 1 ) % 2 , 减 小 空 间 复 杂 度 。 所以把前面都改成i\%2,后面都改成(i+1)\%2,减小空间复杂度。 所以把前面都改成i%2,后面都改成(i+1)%2,减小空间复杂度。
k
其
实
也
能
优
化
,
不
必
要
1
到
5050
,
可
以
根
据
i
和
j
得
到
上
下
限
。
k其实也能优化,不必要1到5050,可以根据i和j得到上下限。
k其实也能优化,不必要1到5050,可以根据i和j得到上下限。
上
限
:
从
i
开
始
的
j
−
1
次
攻
击
加
上
最
后
一
次
为
(
i
+
i
+
j
−
1
)
∗
(
j
−
1
)
/
2
+
n
上限:从i开始的j-1次攻击加上最后一次为(i+i+j-1)*(j-1)/2+n
上限:从i开始的j−1次攻击加上最后一次为(i+i+j−1)∗(j−1)/2+n
下
限
:
从
n
−
j
+
1
开
始
的
j
次
攻
击
为
(
n
−
j
+
1
+
n
)
∗
j
/
2
下限:从n-j+1开始的j次攻击为(n-j+1+n)*j/2
下限:从n−j+1开始的j次攻击为(n−j+1+n)∗j/2
最 后 a n s = m a x ( a n s , d p [ 1 ] [ j ] [ k ] ) 处 理 答 案 即 可 。 最后ans=max(ans,dp[1][j][k])处理答案即可。 最后ans=max(ans,dp[1][j][k])处理答案即可。
这 一 题 其 实 可 以 写 的 , 可 以 说 90 % 都 想 到 了 , 但 是 A C M 就 是 这 样 , 不 允 许 一 点 错 误 , A C 即 是 王 道 。 \red{这一题其实可以写的,可以说90\%都想到了,但是ACM就是这样,不允许一点错误,AC即是王道。} 这一题其实可以写的,可以说90%都想到了,但是ACM就是这样,不允许一点错误,AC即是王道。
Code
#include "bits/stdc++.h"
using namespace std;
typedef long long ll;
ll dp[2][101][5051];
signed main() {
int _; scanf("%d",&_);
while(_--) {
int n; scanf("%d",&n);
memset(dp, 0, sizeof(dp));
vector<ll> a(n + 1), b(n + 1), c(n + 1);
for(int i = 1;i <= n; i++) scanf("%lld%lld%lld",&a[i],&b[i],&c[i]);
dp[n % 2][1][n] = a[n];
for(int i = n - 1;i >= 1; i--) {
for(int j = 1;j <= n - i; j++) {
int down = (i + i + j - 1) * (j - 1) / 2 + n, up = (n - j + 1 + n) * j / 2;
for(int k = down;k <= up; k++) {
dp[i % 2][j + 1][k + i] = max(dp[i % 2][j + 1][k + i], dp[(i + 1) % 2][j][k] + a[i]);
dp[i % 2][j][k] = max(dp[i % 2][j][k], dp[(i + 1) % 2][j][k] + 1ll * j * c[i]);
dp[i % 2][j][k] = max(dp[i % 2][j][k], dp[(i + 1) % 2][j][k] + 1ll * (k - j * i) * b[i]);
}
}
}
ll ans = 0;
for(int j = 1;j <= n; j++) {
for (int k = 0; k <= 5050; k++) {
ans = max(ans, dp[1][j][k]);
}
}
printf("%lld\n",ans);
}
}