2018 EC Finals I. Misunderstood … Missing 逆向dp

2018 EC Finals I. Misunderstood … Missing 逆向dp


传送门: https://codeforces.com/gym/102056/problem/I

题意

刚 开 始 , 有 初 始 值 都 为 0 的 A ( 伤 害 ) 和 D ( 伤 害 增 量 ) 。 刚开始,有初始值都为0的A(伤害)和D(伤害增量)。 0A()D()
D 的 含 义 是 , 每 回 合 刚 开 始 都 可 以 给 A 增 加 D 的 伤 害 。 D的含义是,每回合刚开始都可以给A增加D的伤害。 DAD
有 n 个 回 合 , 每 个 回 合 有 3 个 值 a , b , c 。 有n个回合,每个回合有3个值a,b,c。 n3a,b,c

然 后 三 个 操 作 : 然后三个操作:

  1. 操 作 一 : 可 以 攻 击 , 总 伤 害 增 加 A + a 。 操作一:可以攻击,总伤害增加A+a。 A+a
  2. 操 作 二 : 给 增 量 D 增 加 b 。 操作二:给增量D增加b。 Db
  3. 操 作 三 : 给 伤 害 A 增 加 c 。 操作三:给伤害A增加c。 Ac

问 如 何 操 作 可 以 使 总 伤 害 值 最 大 。 n < = 100 。 问如何操作可以使总伤害值最大。n<=100。 使n<=100

思路

首 先 可 以 想 到 3 100 复 杂 度 的 暴 力 , 显 然 不 行 。 首先可以想到3^{100}复杂度的暴力,显然不行。 3100

然 后 正 向 遍 历 ? 显 然 也 不 行 , 因 为 你 前 面 的 操 作 对 后 续 会 有 影 响 , 所 以 考 虑 逆 向 。 然后正向遍历?显然也不行,因为你前面的操作对后续会有影响,所以考虑逆向。

那 该 怎 么 逆 向 呢 ? 如 果 我 们 知 道 我 们 攻 击 了 几 次 , 并 且 哪 几 次 攻 击 , 就 可 以 算 出 当 前 的 贡 献 。 那该怎么逆向呢?如果我们知道我们攻击了几次,并且哪几次攻击,就可以算出当前的贡献。

比 如 n = 5 , 我 正 在 第 2 天 , 如 果 我 们 第 3 和 第 5 天 攻 击 了 , 那 么 当 前 有 三 个 操 作 : 比如n=5,我正在第2天,如果我们第3和第5天攻击了,那么当前有三个操作: n=5235

  1. 执 行 操 作 一 : a n s + = A + a [ i ] 执行操作一:ans+=A+a[i] ans+=A+a[i]
  2. 执 行 操 作 二 : a n s + = ( 3 − 2 ) ∗ b [ i ] + ( 5 − 2 ) ∗ b [ i ] 执行操作二:ans+=(3-2)*b[i]+(5-2)*b[i] ans+=(32)b[i]+(52)b[i]
  3. 执 行 操 作 三 : a n s + = 2 ∗ b [ i ] 执行操作三:ans+=2*b[i] ans+=2b[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。 100i
攻 击 次 数 ? 最 大 为 100 , j 。 攻击次数?最大为100,j。 100j
哪 几 次 攻 击 ? 这 个 很 不 容 易 想 到 , 那 就 是 记 录 攻 击 次 数 下 标 和 , k 。 哪几次攻击?这个很不容易想到,那就是记录攻击次数下标和,k。 k

那 么 就 得 到 d p [ i ] [ j ] [ k ] 表 示 第 i 轮 , 攻 击 了 j 次 , 并 且 下 标 和 为 k 的 最 大 伤 害 。 那么就得到dp[i][j][k]表示第i轮,攻击了j次,并且下标和为k的最大伤害。 dp[i][j][k]ijk

那 么 就 可 以 进 行 状 态 转 移 了 : 那么就可以进行状态转移了:

  1. 操 作 一 : 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])
  2. 操 作 二 : 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]+(kji)b[i])
  3. 操 作 三 : 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]+jc[i])

所 以 先 逆 序 遍 历 n , 在 遍 历 j , 最 后 遍 历 k 。 所以先逆序遍历n,在遍历j,最后遍历k。 njk

想 到 这 里 就 已 经 很 好 了 , 可 能 是 我 们 没 有 精 力 继 续 想 了 , 没 有 优 化 。 所 以 一 直 过 不 去 。 想到这里就已经很好了,可能是我们没有精力继续想了,没有优化。所以一直过不去。

我 们 知 道 n 有 100 , 所 以 100 ∗ 100 ∗ ( 1 + 100 ) ∗ 100 / 2 会 M L E , 然 后 看 到 i 其 实 可 以 滚 动 的 。 我们知道n有100,所以100*100*(1+100)*100/2会MLE,然后看到i其实可以滚动的。 n100100100(1+100)100/2MLEi

所 以 把 前 面 都 改 成 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得到上下限。 k15050ij
上 限 : 从 i 开 始 的 j − 1 次 攻 击 加 上 最 后 一 次 为 ( i + i + j − 1 ) ∗ ( j − 1 ) / 2 + n 上限:从i开始的j-1次攻击加上最后一次为(i+i+j-1)*(j-1)/2+n ij1(i+i+j1)(j1)/2+n
下 限 : 从 n − j + 1 开 始 的 j 次 攻 击 为 ( n − j + 1 + n ) ∗ j / 2 下限:从n-j+1开始的j次攻击为(n-j+1+n)*j/2 nj+1j(nj+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%ACMAC

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);
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值