先不考虑秒杀两台机器的情况,要使得损失最小。处理出每个兵器能抗 t [ i ] = ⌊ d i − 1 A T K ⌋ + 1 t[i] = \lfloor\frac{d_i - 1}{ATK}\rfloor +1 t[i]=⌊ATKdi−1⌋+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;
}