2018ACM-ICPC EC-Final 现场赛I题 Misunderstanding...Missing 倒着DP

(有任何问题欢迎留言或私聊 && 欢迎交流讨论哦

Catalog

Problem:

 很多acm群里有题面PDF了,我就不赘述了。简单说一下,你有n次操作,每次操作有3种选择,1.造成A+ai点伤害;2.永久给D增加bi;3.永久给A增加ci。(每次操作前执行:A+=D)。问最后最多造成多少伤害。

Solution:

 这么明显的dp却没有想到,真实太菜了。当时想到要从后往前推,但就是卡着不知道怎么转移最优,如何平衡3种操作。赛后看到qls的状态定义,终于理解qls的意思了。菜是原罪啊。

状态表示:
d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]表示从后开始执行到第 i i i步,执行了 j j j次操作1,选择操作1的下标和为 k k k所造成的最高伤害。

初始化:

memset(dp, -1, sizeof(dp));//必须初始化为-1,因为你要确保状态的正确性,也就是j次操作1的下标和真的是k才行。
dp[n][1][n] = cw[n].a;//因为最后一次肯定是要打伤害的

状态转移方程:
d p [ i ] [ j ] [ k ] = m a x ( d p [ i + 1 ] [ j ] [ k ] + m a x ( c ∗ j , b ∗ ( k − i ∗ j ) ) , d p [ i + 1 ] [ j − 1 ] [ k − i ] + a ) ; dp[i][j][k] = max(dp[i+1][j][k]+max(c*j, b*(k-i*j)), dp[i+1][j-1][k-i]+a); dp[i][j][k]=max(dp[i+1][j][k]+max(cj,b(kij)),dp[i+1][j1][ki]+a);

  • 第一部分是第 i i i次选择操作2或3,第二部分是第 i i i次选择操作1。
  • 如果这一次选择操作3,显然后面的 j j j次伤害需要增加 c ∗ j c*j cj
  • 同理:选择操作2,后面每次操作需要增加 b ∗ ( x − i ) b*(x-i) b(xi)点伤害, x x x是选择操作1的编号,求个和之后就是 b ∗ ( k − i ∗ j ) b*(k-i*j) b(kij)了。
  • 到这里,这题就解决啦。

AC_Code:

第一个代码爆内存了,其他两个都可以ac

//这份代码常数应该挺大,注意优化一下
#include<bits/stdc++.h>

using namespace std;
typedef long long LL;

const int MXN = 1e5 + 5;

int n, m;
LL dp[101][101][5052];
struct lp{
    int a, b, c;
}cw[105];

int main(int argc, char const *argv[]) {
    int tim; scanf("%d", &tim);
    while(tim --) {
        scanf("%d", &n);
        for(int i = 1; i <= n; ++i) {
            scanf("%d%d%d", &cw[i].a, &cw[i].b, &cw[i].c);
        }
        memset(dp, -1, sizeof(dp));
        dp[n][1][n] = cw[n].a;
        int tmp = n*(n+1)/2;
        for(int i = n-1; i >= 1; --i) {
            for(LL j = n-i+1,a=cw[i].a,b=cw[i].b,c=cw[i].c; j >= 1; --j) {
                for(int k = tmp; k >= n; --k) {
                    if(dp[i+1][j][k] != -1) dp[i][j][k] = dp[i+1][j][k]+max(c*j, b*(k-i*j));
                    if(k >= i+n &&j >= 2&& dp[i+1][j-1][k-i] != -1)
                        dp[i][j][k] = max(dp[i][j][k], dp[i+1][j-1][k-i]+a);
                }
            }
        }
        LL ans = 0;
        for(int i = 1; i <= n; ++i) {
            for(int j = 1; j <= tmp; ++j) {
                ans = max(ans, dp[1][i][j]);
            }
        }
        printf("%lld\n", ans);
    }
    return 0;
}

滚动数组:

#include<bits/stdc++.h>
#define A cw[i].a
#define B cw[i].b
#define C cw[i].c
using namespace std;
typedef long long LL;
const int INF = 0x3f3f3f3f;
const int MXN = 1e5 + 7;

int n, m;
struct lp{
    LL a, b, c;
}cw[105];
LL dp[5051][101];

int main() {
    int tim; scanf("%d", &tim);
    while(tim --) {
        scanf("%d", &n);
        m = n*(n+1)/2;
        for(int i = 0; i <= m; ++i) {
            for(int j = 0; j <= n; ++j) {
                dp[i][j] = -1;
            }
        }
        for(int i = 1; i <= n; ++i) scanf("%lld%lld%lld", &A, &B, &C);
        dp[n][1] = cw[n].a;
        //dp[0][0] = 0;
        for(int i = n-1; i >= 1; --i) {
            for(int k = m; k >= n; --k) {
                for(int j = n-i+1; j >= 1; --j) {
                    if(dp[k][j] != -1) dp[k][j] += max(j*C,(k-i*j)*B);
                    if(k-i>=n&&j>=2&&dp[k-i][j-1] != -1) {
                        dp[k][j] = max(dp[k][j], dp[k-i][j-1] + A);
                    }
                }
            }
        }
        LL ans = 0;
        for(int i = 1; i <= m; ++i) {
            for(int j = 1; j <= n; ++j) {
                ans = max(ans, dp[i][j]);
            }
        }
        printf("%lld\n", ans);
    }    
    return 0;
}

如果你不会滚动数组,也可以这样优化空间:

#include<bits/stdc++.h>
#define A cw[i].a
#define B cw[i].b
#define C cw[i].c
using namespace std;
typedef long long LL;
const int INF = 0x3f3f3f3f;
const int MXN = 1e5 + 7;

int n, m;
struct lp{
    LL a, b, c;
}cw[105];
LL dp[5051][101][2];

int main() {
    int tim; scanf("%d", &tim);
    while(tim --) {
        scanf("%d", &n);
        m = n*(n+1)/2;
        for(int i = 0; i <= m; ++i) {
            for(int j = 0; j <= n; ++j) {
                dp[i][j][0] = dp[i][j][1] = -1;
            }
        }
        for(int i = 1; i <= n; ++i) scanf("%lld%lld%lld", &A, &B, &C);
        dp[n][1][0] = cw[n].a;
        //dp[0][0][0] = 0;
        int p = 1, q = 0;
        for(int i = n-1; i >= 1; --i) {
            for(int k = m; k >= n; --k) {
                for(int j = n-i+1; j >= 1; --j) {
                    if(k-i>=n&&j>=2&&dp[k-i][j-1][q] != -1) dp[k][j][p] = dp[k-i][j-1][q] + A;
                    if(dp[k][j][q] != -1) {
                        dp[k][j][p] = max(dp[k][j][p], dp[k][j][q]+max(j*C,(k-i*j)*B));
                    }
                }
            }
            p = !p; q = !q;
        }
        LL ans = 0;
        for(int i = 1; i <= m; ++i) {
            for(int j = 1; j <= n; ++j) {
                ans = max(ans, dp[i][j][q]);
            }
        }
        printf("%lld\n", ans);
    }    
    return 0;
}

Problem Description:
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值