[BZOJ4700]适者

[BZOJ4700]适者

题目大意:

\(n(n\le3\times10^5)\)个敌人,每个敌人有一个攻击力\(v_i\)和一个防御力\(d_i\)。你的攻击力是\(a\)。战斗看做回合制,每回合进程如下:

  1. 你选择某个敌人进行攻击,令其防御力减少\(a\),若防御力\(<0\)则该敌人被击败。
  2. 所有存活的敌人每人对你造成\(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;
}
posted @ 2018-07-19 20:33  skylee03  阅读(191)  评论(0编辑  收藏  举报