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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章