hdu3851.Beat It!

http://acm.hdu.edu.cn/showproblem.php?pid=3851

题意:有一只怪兽,在白天攻击一次能损失它pd点血,晚上攻击则损失它pn点血,每次攻击的间隔为t,每天的白天时间与晚上时间分别为t1i, t2i,问N天内最多攻击它多少血。n <= 1000, t <= 100

思路:dp,先把问题等价为n×2段,每段的价值为相应的pd或者为pn,不妨都记为pi,显然每次记录一个时间段最后t时间的攻击状况即可,f[i][j]表示第i个时间段最后一次攻击在第tt[i]-j的最优值,则转移方程为

f[i][j] = max{ g[p[i][j]] + ((tt[i] - j - 1)/t+1) * pi), h[p[i][j]+1][k] + ((tt[i] - j - 1) / t + 1), g[i - 1] + ((tt[i] - j - 1) / t) };

其中,g[i]为前i段所有情况中最优的结果,p[i][j]=max{p`| tt[p` + 1] + tt[p` + 2] + ... + tt[i - 1] + (tt[i] - j - 1) % t >= t }, k满足k + tt[p + 2] + tt[p + 3] + ... + tt[i - 1] + (tt[i] - j - 1) % t = t,h[p][k] = max{ f[p][k], f[p][k + 1], ... , f[p][t - 1]}

这个方程的意思是找以下情况中最大的

(1) 前p段最大的,然后满足tt[p + 1] + tt[p + 2] + ... + tt[i - 1] + (tt[i] - j - 1) % t  >= t, 所以在第i段从任意点开始攻击,最多攻击((tt[i] - j - 1)/t+1)次;

(2) 在第p+1段,最后, 最后一次攻击在第k次,因为 k满足k + tt[p + 2] + tt[p + 3] + ... + tt[i - 1] + (tt[i] - j - 1) % t = t, 所以同理在第i段从任意点开始攻击,最多攻击((tt[i] - j - 1)/t+1)次;

(3) 在第i段放少一个,那么必然可以使得第一段末尾至少可以空出t-1的时间点出来,也就肯定不会跟第i-1的时间段的攻击产生冲突。


列好方程之后首先预处理出p和k,

因为如果固定最后攻击的时间,固定次数(最大可能情况), 那么肯定尽可能放在后面攻击,所以如果对于这样的一个固定的区间,最迟第一次攻击可以在区间(tt[i] - j - 1) % t 后,而且,显然(tt[i] - j1 - 1) % t > (tt[i] - j2 - 1) % 2, p[i][j1] > p[i][j2], 所以先在线性时间内求出所以t2[r], 则p[i][j] = t2[(tt[i] - j - 1) % t ];

k = t - (tt[p + 2] + tt[p + 3] + ... + tt[i - 1] + (tt[i] - j - 1) % t)。

此时转移时间在预处理之后能在O(1)时间内完成,最后时间复杂度为O(nt).

其实若t = 1, pn = pd = 2 ^ 31 - 1, tt[i] = 2 ^ 31. 那么答案应该会超出long long的范围,不过我尝试不写高精度过了。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

#define sqr(x) ((x) * (x))
#define two(x) (1 << (x))
#define Rep(i, s, n) for (int i = s; i < (n); ++i)
#define X first
#define Y second

typedef long long LL;

const int MAXN = 2012;
const double eps = 1e-9;
const int INF = 1000000000;

int n, t, pn, pd, tt[MAXN];
LL f[MAXN][200], g[MAXN], ss[MAXN];
int pre[MAXN][200], tmp[200];

void init()
{
    scanf("%d%d%d%d", &n, &t, &pd, &pn);
    Rep(i, 0, 2 * n) scanf("%d", &tt[i + 1]);
    ss[0] = 0;
    Rep(i, 0, 2 * n) ss[i + 1] = ss[i] + tt[i + 1];
    Rep(i, 1, 2 * n + 1)
    {
        tmp[0] = 0;
        Rep(j, 0, i) if (ss[i - 1] - ss[i - j - 1] >= t)
        {
            tmp[0] = i - j - 1;
            break;
        }
        Rep(j, 1, min(t, tt[i]))
        {
            tmp[j] = tmp[j - 1];
            while (ss[i - 1] - ss[tmp[j] + 1] + j >= t) ++tmp[j];
        }
        int rem = tt[i] % t;
        Rep(j, 0, min(t, tt[i]))
        {
            rem = (rem + t - 1) % t;
            pre[i][j] = tmp[rem];
        }
    }
}

void work()
{
    g[0] = 0;
    Rep(i, 1, n * 2 + 1)
    {
        LL pt = ((i & 1)? pd: pn);
        Rep(j, 0, min(t, tt[i]))
        {
            f[i][j] = g[pre[i][j]] + pt * ((tt[i] - j - 1) / t + 1);
            int rem = (tt[i] - j - 1) % t;
            if (rem + ss[i - 1] >= t)
                f[i][j] = max(f[i][j], f[pre[i][j] + 1][t - 1 - rem] + pt * ((tt[i] - j - 1) / t + 1));
            if (tt[i] - j - 1 >= t)
                f[i][j] = max(f[i][j], g[i - 1] + pt * ((tt[i] - j - 1) / t));
        }
        Rep(j, 1, min(t, tt[i]))
        {
            f[i][min(t, tt[i]) - j - 1] = max(f[i][min(t, tt[i]) - j - 1], f[i][min(t, tt[i]) - j]);
        }
        g[i] = max(g[i - 1], f[i][0]);
    }
    cout << g[n * 2] << endl;
}

int main()
{
    int T, ca = 0;
    scanf("%d", &T);
    while (T--)
    {
        printf("Case %d: ", ++ca);
        init();
        work();
    }
    return 0;
}


然后看着今天上海的board,觉得自己没去好幸福……


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值