bzoj 4700. 适者(李超线段树 + 思维)

在这里插入图片描述


先不考虑秒杀两台机器的情况,要使得损失最小。处理出每个兵器能抗 t[i]=di1ATK+1t[i] = \lfloor\frac{d_i - 1}{ATK}\rfloor +1 刀。

根据贡献排序:v[i] * (t[i] - 1) + v[j] * (t[i] + t[j] - 1) < v[j] * (t[j] - 1) + v[i] * (t[i] + t[j] - 1)
整理可以得到:v[j] * t[i] < v[i] * t[j]

预处理抗刀数前缀和 preT[i] ,攻击力后缀和 sufV[i]
考虑秒杀一个人形兵器,它对总答案的贡献减少 preT[i - 1] * v[i] + sufV[i] * t[i] - v[i]

由于秒杀两个人形兵器减少的贡献互相有影响,不能单次贪心,每次秒杀贡献最多的一个人形兵器。
列出秒杀两个人形兵器的贡献减少式子,考虑要秒杀的人形兵器分别为 i,j,其中 i < j

先秒杀 i,后秒杀 j,根据秒杀一个人形兵器的式子,可以得到秒杀 i,j 减少的贡献为:preT[i - 1] * v[i] + sufV[i] * t[i] - v[i] + (preT[j - 1] - t[i]) * v[j] + sufV[j] * t[j] - v[j]

c[i] = preT[i - 1] * v[i] + sufV[i] * t[i] - v[i]

整理得到 :c[i] + c[j] - t[i] * v[j]
考虑 枚举 i,要找一个 j 使得 c[j] - t[i] * v[j] 最大,将 c[j] - t[i] * v[j] 当成一次函数,变量为 t[i],这个显然可以用 李超树来维护,于是可以逆序枚举 i,每次动态加入一条线段,单点查询 这些线段在 t[i] 处的最大值

(还有CDQ分治 + dp斜率优化的做法,CDQ的作用主要是维护一下单调性,方便dp 转移。。)


代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 3e5 + 10;
const int N = 1e4 + 10;
#define inf 0x3f3f3f3f
typedef long long ll;
int n,atk;
struct weapon {
	int v,t;
	weapon() {};
	weapon(int ai,int bi) {
		v = ai;
		t = bi;
	}
	bool operator < (const weapon &rhs) const {
		return t * rhs.v < rhs.t * v;
	}
}b[maxn];
struct Line {				//直线结构体 
	ll k,b;				 
	Line() {}
	Line(ll ki,ll bi) {
		k = ki, b = bi;
	}
	ll calc(int x) {	//计算在 x 点的 y值 
		return 1ll * k * x + b;
	}
};
struct seg_tree {					//维护 x = k 处最低线段 
	#define lson rt << 1,l,mid
	#define rson rt << 1 | 1,mid + 1,r
	Line line[N << 2];
	void build(int rt,int l,int r) {
		line[rt].k = 0; line[rt].b = 0;
		if (l == r) return;
		int mid = l + r >> 1;
		build(lson); build(rson);
	}
	void update(int rt,int l,int r,int L,int R,Line t) {
		if (L <= l && r <= R) {
			int mid = l + r >> 1;
			if (line[rt].calc(l) < t.calc(l) && line[rt].calc(r) < t.calc(r)) {		
				line[rt] = t;
			} else if (line[rt].calc(l) < t.calc(l) || line[rt].calc(r) < t.calc(r)) {		
				if (line[rt].calc(mid) < t.calc(mid)) {					
					Line tmp = t; t = line[rt]; line[rt] = tmp;
				}
				if (t.k > line[rt].k) {						
					update(rson,L,R,t);
				} else {									
					update(lson,L,R,t);
				}
			}
		} else {
			int mid = l + r >> 1;
			if (L <= mid) update(lson,L,R,t);
			if (mid + 1 <= R) update(rson,L,R,t);
		}
	}
	ll query(int rt,int l,int r,int v) {		//查询区间 L,R 最小值 
		if (l == r) return line[rt].calc(v);
		ll ans = line[rt].calc(v);
		int mid = l + r >> 1;
		if (v <= mid) return max(ans,query(lson,v));
		else return max(ans,query(rson,v)); 
	}
}seg;
ll sufV[maxn],preT[maxn],C[maxn];
int main() {
	scanf("%d%d",&n,&atk);
	for (int i = 1,x,y; i <= n; i++) {
		scanf("%d%d",&x,&y);
		int t = (y - 1) / atk + 1;
		b[i] = weapon(x,t);
	}
	sort(b + 1,b + n + 1);
	for (int i = 1; i <= n; i++) {
		preT[i] = preT[i - 1] + b[i].t;
	}
	for (int i = n; i >= 1; i--) {
		sufV[i] = sufV[i + 1] + b[i].v;
	}
	ll ans = 0,res = 0;
	for (int i = 1; i <= n; i++) {
		res += 1ll * preT[i] * b[i].v - b[i].v;
		C[i] = 1ll * preT[i - 1] * b[i].v + 1ll * sufV[i] * b[i].t - b[i].v;
	}
	ans = 0;
	seg.update(1,1,N,1,N,Line(-b[n].v,C[n]));
	for (int i = n - 1; i >= 1; i--) {
		ans = max(ans,C[i] + seg.query(1,1,N,b[i].t));
		seg.update(1,1,N,1,N,Line(-b[i].v,C[i]));
	}
	printf("%lld\n",res - ans);
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章