[bzoj4700]适者

Description

给出n个人,每个人有血量T和攻击力D。
你自己可以看做有无限血量和A的攻击力。
战斗为回合制进行,每一回合你先选择一个敌人攻击,将其血量减少你的攻击力的数值。
若一个人的血量<=0则死亡。
然后所有存活的敌人对你进行攻击,每个人对你造成D的伤害。
在战斗开始前你可以先秒杀两个敌人。
求你所受伤害的最小值。
n<=3*10^5,T,D,A<=10^4

Solution

显然血量可以直接看做T/A+(T%A>0)
首先考虑没有秒杀的情况。
显然我们每次都会选定一个人一直攻击。
那么我们就是要求出最优的攻击顺序。
假设我们已经找出了一种攻击顺序,那么交换相邻两项会使答案更优的话满足

(Ti1)(Di+Di+1)+Ti+1Di+1>(Ti+11)(Di+Di+1)+TiDi

化简可得 TiDi>Ti+1Di+1
那么就按这个作为偏序关系来排序就是要求的顺序了。
接下来考虑秒杀。
可以预处理Ci表示秒掉i会使答案减少多少,那么枚举i,j表示要秒杀的人,保证i在偏序关系中在j之前,那么删掉i,j对答案的影响是Ci+Cj-Ti*Dj
因为多算了一遍所以要减掉。
那么我们就是要求max(Ci+Cj-Ti*Dj)
发现如果我们按Dj降序排序,Ti升序排序,那么是可以用单调队列斜率优化来做到O(n)的。
但是这样就不一定满足i在j前面了,怎么办呢?
CDQ分治喽~用左边区间的去更新右边区间的。
排序用归并排序,就可以做到O(N log N)了。

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
typedef double db;
typedef long long ll;
const int N=3*1e5+5;
struct note{int d,t;}a[N];
struct node{int v;ll c;}t[N],T[N],d[N],D[N];
int n,atk,q[N];
ll c[N],sum,ans;
bool cmp(note x,note y) {
    return (db)x.t/x.d<(db)y.t/y.d;
}
db calc(int x,int y) {
    if (t[x].v==t[y].v) {
        if (t[y].c<=t[x].c) return 0;
        else return 1e18; 
    }
    return (db)(t[y].c-t[x].c)/(t[y].v-t[x].v);
}
void solve(int l,int r) {
    if (l==r) return;
    int mid=(l+r)/2;
    solve(l,mid);solve(mid+1,r);
    int le=1,ri=0;
    fo(i,l,mid) {
        while (le<ri&&calc(q[ri-1],q[ri])<calc(q[ri],i)) ri--;
        q[++ri]=i;
    }
    fo(i,mid+1,r) {
        while (le<ri&&calc(q[le],q[le+1])>=d[i].v) le++;
        ans=min(ans,sum-d[i].c-t[q[le]].c+t[q[le]].v*d[i].v);
    }
    int i=l,j=mid+1,tot=l;
    while (i<=mid&&j<=r) 
        if (d[i].v>d[j].v) D[tot++]=d[i++];
        else D[tot++]=d[j++];
    while (i<=mid) D[tot++]=d[i++];
    while (j<=r) D[tot++]=d[j++];
    i=l,j=mid+1,tot=l;
    while (i<=mid&&j<=r) 
        if (t[i].v<t[j].v) T[tot++]=t[i++];
        else T[tot++]=t[j++];
    while (i<=mid) T[tot++]=t[i++];
    while (j<=r) T[tot++]=t[j++];
    fo(i,l,r) d[i]=D[i],t[i]=T[i];
}
int main() {
    scanf("%d%d",&n,&atk);
    fo(i,1,n) scanf("%d%d",&a[i].d,&a[i].t),a[i].t=a[i].t/atk+(a[i].t%atk>0);
    sort(a+1,a+n+1,cmp);
    ll res=0;
    fo(i,1,n) {
        res+=(ll)a[i].t;
        c[i]+=(ll)(res-1)*a[i].d;
    }
    fo(i,1,n) sum+=c[i];
    ans=sum;res=0;
    fd(i,n,1) {
        c[i]+=(ll)res*a[i].t;
        res+=(ll)a[i].d;
    }
    fo(i,1,n) d[i].v=a[i].d,t[i].v=a[i].t,d[i].c=t[i].c=c[i];
    solve(1,n); 
    printf("%lld\n",ans);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值