[BZOJ4700]适者
题目大意:
有(n(nle3 imes10^5))个敌人,每个敌人有一个攻击力(v_i)和一个防御力(d_i)。你的攻击力是(a)。战斗看做回合制,每回合进程如下:
- 你选择某个敌人进行攻击,令其防御力减少(a),若防御力(<0)则该敌人被击败。
- 所有存活的敌人每人对你造成(v_i)点损失。
战斗开始前你有机会直接去掉对方的两个敌人。
你拥有无限的血量,请最小化你的损失。
思路:
首先不考虑去掉两个敌人的情况。
显然我们可以按一定顺序依次击败各个敌人,而不必一个人打一半就去打其他人。
用(t_i)表示击败敌人(i)所花的时间,则(t_i=lceilfrac{d_i}a ceil)。
考虑攻击的顺序。(i)在(j)前面,当且仅当(t_i imes v_j<t_j imes v_i)。
此时(ans=sum_{i=1}^n(sum_{j=1}^{i}t_j-1) imes v_i)。
现在考虑事先去掉两个敌人的情况,即如何选择去掉的敌人使损失最小。
用(pre)表示(t_i)的前缀和,用(suf)表示(v_i)的后缀和。
考虑只去掉一个敌人的情况,去掉(i)这个敌人可以使答案减少(c_i=suf[i] imes t_i+(pre[i-1]-1) imes v_i)。
而去掉两个人就是要找到一对(i,j),使得(c_i+c_j-v_i imes t_j)最大。
考虑固定一个(i),则(j)的贡献就是一个关于(v_i)的一次函数,用李超树或CHT维护凸壳即可。
时间复杂度(mathcal O(nlog n))。
源代码:
#include<cmath>
#include<cstdio>
#include<cctype>
#include<algorithm>
inline int getint() {
register char ch;
while(!isdigit(ch=getchar()));
register int x=ch^'0';
while(isdigit(ch=getchar())) x=(((x<<2)+x)<<1)+(ch^'0');
return x;
}
typedef long long int64;
const int N=3e5+2,LIM=1e4;
struct Role {
int v,t;
bool operator < (const Role &rhs) const {
return t*rhs.v<rhs.t*v;
}
};
Role r[N];
int64 pre[N],suf[N];
class SegmentTree {
#define _left <<1
#define _right <<1|1
#define mid ((b+e)>>1)
private:
struct Node {
int64 a,b;
int left,right;
};
Node node[N<<2];
public:
void insert(const int &p,const int &b,const int &e,int64 i,int64 j) {
if(node[p].a==0&&node[p].b==0) {
node[p].a=i;
node[p].b=j;
return;
}
const int64 lval1=i*b+j,rval1=i*e+j;
const int64 lval2=node[p].a*b+node[p].b;
const int64 rval2=node[p].a*e+node[p].b;
if(lval1>=lval2&&rval1>=rval2) {
node[p].a=i;
node[p].b=j;
return;
}
if(b==e) return;
const double c=1.*(node[p].b-j)/(i-node[p].a);
if(c<mid&&rval1>rval2) {
insert(p _left,b,mid,node[p].a,node[p].b);
node[p].a=i;
node[p].b=j;
return;
}
if(c<mid&&rval1<=rval2) {
insert(p _left,b,mid,i,j);
return;
}
if(c>=mid&&lval1>lval2) {
insert(p _right,mid+1,e,node[p].a,node[p].b);
node[p].a=i;
node[p].b=j;
return;
}
if(c<mid&&lval1<=lval2) {
insert(p _right,mid+1,e,i,j);
return;
}
}
int64 query(const int &p,const int &b,const int &e,const int &x) {
if(node[p].a==0&&node[p].b==0) return 0;
int64 ret=node[p].a*x+node[p].b;
if(b==e) return ret;
if(x<=mid) ret=std::max(ret,query(p _left,b,mid,x));
if(x>mid) ret=std::max(ret,query(p _right,mid+1,e,x));
return ret;
}
#undef _left
#undef _right
#undef mid
};
SegmentTree t;
int main() {
const int n=getint(),atk=getint();
for(register int i=1;i<=n;i++) {
const int a=getint(),d=getint();
const int t=ceil(1.*d/atk);
r[i]=(Role){a,t};
}
std::sort(&r[1],&r[n]+1);
for(register int i=1;i<=n;i++) pre[i]=pre[i-1]+r[i].t;
for(register int i=n;i>=1;i--) suf[i]=suf[i+1]+r[i].v;
int64 ans=0;
for(register int i=1;i<=n;i++) {
const int64 tmp=suf[i]*r[i].t+(pre[i-1]-1)*r[i].v;
if(i>1) ans=std::max(ans,tmp+t.query(1,1,LIM,r[i].v));
t.insert(1,1,LIM,-r[i].t,tmp);
}
ans=-ans;
for(register int i=1;i<=n;i++) {
ans+=(pre[i]-1)*r[i].v;
}
printf("%lld
",ans);
return 0;
}