BZOJ4141 THUSC2013 魔塔 贪心

没得传送门


考虑当\(Atk\)增大时,\(Def\)一定越来越没用,因为回合数在变少。所以考虑从小到大枚举\(Atk\)然后双指针计算。

设\(f_i(x)\)表示在\(Atk = i\)时,\(Def\)从\(x-1\)到\(x\)时可以减少的血量的数量,易知\(f_i(x) \leq f_i(x - 1) , f_i(x) \leq f_{i-1}(x)\)。对于每一个怪,当\(Atk\)确定之后,它的攻击回合数就确定了。对于\(Atk = i\),一个怪物的攻击力为\(atk\),攻击回合数为\(T\)时,相当于在函数\(f_i(x)\)上\([1,atk]\)区间做一个前缀加\(T\)的操作。当攻击回合数减小的时候也相当于前缀减然后前缀加。

不妨把这个问题变得更形象一点:有若干条线段,左端点为\(1\),右端点不固定,每一条线段都有一个权值。从\(Def = x - 1\)到\(Def = x\)能够减少的血量就是覆盖了\(x\)点的所有线段的权值和。

一件显然的事情是因为\(Def\)在不断变小,所以之前被当前点覆盖的线段在之后也一定被覆盖。

所以我们可以设\(num_i\)表示右端点为\(i\)的所有线段的权值之和,然后我们考虑:当当前的\(Def\)覆盖的线段的权值之和\(\leq cd\)时将\(Def - 1\),然后把\(num_{Def - 1}\)加入,就可以更新出新的权值了。对于删线段和加线段只需要看一下当前加入线段的右端点和\(Def\)的关系并进行更新就可以了。在维护攻击防御值的时候同时维护血量以保证复杂度。

那么最后的问题就是如何快速加入和删除线段,暴力做是\(O(NHp)\)的,但是注意到\(\lceil \frac{Hp}{i} \rceil = \lfloor \frac{Hp - 1}{i} \rfloor + 1\)只有\(\sqrt{Hp}\)种取值,我们可以预处理出每一个转移点,就可以做到\(O(\sqrt{Hp}N)\)了

可能比较卡空间,慎用STL

然后这道题BZOJ上没有SPJ,面向数据编程可以知道:对于所有可行答案,取其中Atk最大的;对于所有Atk一样大的,取Def最大的。

#include<bits/stdc++.h>
//this code is written by Itst
using namespace std;

int read(){
    int a = 0; char c = getchar();
    while(!isdigit(c)) c = getchar();
    while(isdigit(c)){
        a = a * 10 + c - 48; c = getchar();
    }
    return a;
}

const int _ = 2e6 + 7;
#define PII pair < int , int >
struct node{
    int nxt , rht , val;
}now[_ * 5 + 5000];
int head[_] , N , CA , CD , cnt , MXD;
long long num[_] , Ans , HP , ATK , DEF , sum , X , Y , Z;

void push(int rht , int val , int id){
    now[++cnt] = (node){head[id] , rht , val};
    head[id] = cnt;
}

void modify(int plc , int val){
    num[plc] += val;
    if(Y <= plc){sum += val; Z += (plc - Y) * val;}
}

int main(){
    N = read(); CA = read(); CD = read();
    for(int i = 1 ; i <= N ; ++i){
        int h = read() - 1 , a = read() , d = read();
        for(int j = 1 , pj ; j <= h ; j = pj + 1){
            pj = h / (h / j);
            if(j == 1) push(a , h + 1 , d + 1);
            else push(a , h / j - h / (j - 1) , d + j);
        }
        push(a , -1 , h + d + 1);
        MXD = max(MXD , d);
    }
    Ans = 1e18; Y = 1e6; Z = 1;
    for(X = 1 ; X <= 2e6 ; ++X){
        for(int i = head[X] ; i ; i = now[i].nxt)
            modify(now[i].rht , now[i].val);
        if(X <= MXD) continue;
        while(Y > 1 && sum < CD){
            Z += sum; sum += num[--Y];
        }
        if(Z + X * CA + Y * CD <= Ans){
            Ans = Z + X * CA + Y * CD;
            ATK = X; DEF = Y; HP = Z;
        }
    }
    cout << HP << ' ' << ATK << ' ' << DEF;
    return 0;
}
上一篇:c++基础


下一篇:【DP优化】【P1430】序列取数