先不考慮秒殺兩臺機器的情況,要使得損失最小。處理出每個兵器能抗 刀。
根據貢獻排序: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;
}