[BZOJ4700]适者
[BZOJ4700]适者
题目大意:
有\(n(n\le3\times10^5)\)个敌人,每个敌人有一个攻击力\(v_i\)和一个防御力\(d_i\)。你的攻击力是\(a\)。战斗看做回合制,每回合进程如下:
- 你选择某个敌人进行攻击,令其防御力减少\(a\),若防御力\(<0\)则该敌人被击败。
- 所有存活的敌人每人对你造成\(v_i\)点损失。
战斗开始前你有机会直接去掉对方的两个敌人。
你拥有无限的血量,请最小化你的损失。
思路:
首先不考虑去掉两个敌人的情况。
显然我们可以按一定顺序依次击败各个敌人,而不必一个人打一半就去打其他人。
用\(t_i\)表示击败敌人\(i\)所花的时间,则\(t_i=\lceil\frac{d_i}a\rceil\)。
考虑攻击的顺序。\(i\)在\(j\)前面,当且仅当\(t_i\times v_j<t_j\times v_i\)。
此时\(ans=\sum_{i=1}^n(\sum_{j=1}^{i}t_j-1)\times v_i\)。
现在考虑事先去掉两个敌人的情况,即如何选择去掉的敌人使损失最小。
用\(pre\)表示\(t_i\)的前缀和,用\(suf\)表示\(v_i\)的后缀和。
考虑只去掉一个敌人的情况,去掉\(i\)这个敌人可以使答案减少\(c_i=suf[i]\times t_i+(pre[i-1]-1)\times v_i\)。
而去掉两个人就是要找到一对\(i,j\),使得\(c_i+c_j-v_i\times t_j\)最大。
考虑固定一个\(i\),则\(j\)的贡献就是一个关于\(v_i\)的一次函数,用李超树或CHT维护凸壳即可。
时间复杂度\(\mathcal O(n\log 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\n",ans);
return 0;
}