240102 拳坛咋提 斜率优化
有一种,怎么说,人生追求就要实现的感觉。
这么一想突然感觉自己好没追求啊 hhh
A. 小 P 的牧场
http://222.180.160.110:1024/contest/4624/problem/1
令 \(f_i\) 表示在 \(i\) 处安一个控制站的最小总费用。那么有:
然后条件反射令 \(c_i=\sum\limits_{j=1}^i b_j\times (i-j)=c_{i-1}+s_{i-1}\),其中 \(s\) 为 \(b\) 的前缀和。
则有:
分参,整理得 \(f_i=\min\{-s_j\times i+s_j\times j-c_j+f_j\}+a_i+c_i\),以 \(k=-s_j\),\(b=s_j\times j-c_j+f_j\) 维护李超,\(x=i\) 时的最低交点即为 \(\min\) 内值。
空间有点卡,注意一下就好了。
#define int long long
namespace XSC062 {
using namespace fastIO;
const int inf = 1e18;
const int maxn = 1e6 + 5;
const int maxm = 1e6 + 5;
#define lt (p << 1)
#define rt (lt | 1)
struct __ { int k, b; };
struct _ { int l, r, u; };
int n, tot;
__ seg[maxn];
_ t[maxm << 2];
int f[maxn], s[maxn];
int a[maxn], b[maxn], c[maxn];
int min(int x, int y) {
return x < y ? x : y;
}
void swap(int &x, int &y) {
x ^= y ^= x ^= y;
return;
}
void bld(int p, int l, int r) {
t[p].l = l, t[p].r = r, t[p].u = 0;
if (l == r) return;
int mid = (l + r) >> 1;
bld(lt, l, mid), bld(rt, mid + 1, r);
return;
}
int getv(int id, int x) {
if (!id) return inf;
return x * seg[id].k + seg[id].b;
}
void upd(int p, int id) {
if (!t[p].u) { t[p].u = id; return; }
int mid = (t[p].l + t[p].r) >> 1;
int v1 = getv(t[p].u, mid),
v2 = getv(id, mid);
if (v2 < v1) swap(t[p].u, id);
v1 = getv(t[p].u, t[p].l);
v2 = getv(id, t[p].l);
if (v2 < v1) upd(lt, id);
v1 = getv(t[p].u, t[p].r);
v2 = getv(id, t[p].r);
if (v2 < v1) upd(rt, id);
return;
}
void add(int p, int l, int r, int id) {
if (l <= t[p].l && t[p].r <= r) {
upd(p, id);
return;
}
int mid = (t[p].l + t[p].r) >> 1;
if (l <= mid) add(lt, l, r, id);
if (r > mid) add(rt, l, r, id);
return;
}
int ask(int p, int x) {
int res = inf;
if (t[p].u) res = getv(t[p].u, x);
if (t[p].l == t[p].r) return res;
int mid = (t[p].l + t[p].r) >> 1;
if (x <= mid) res = min(res, ask(lt, x));
else res = min(res, ask(rt, x));
return res;
}
int main() {
read(n);
bld(1, 1, n);
add(1, 1, n, ++tot);
for (int i = 1; i <= n; ++i) read(a[i]);
for (int i = 1; i <= n; ++i) {
read(b[i]), s[i] = s[i - 1] + b[i];
c[i] = c[i - 1] + s[i - 1];
f[i] = ask(1, i) + a[i] + c[i];
seg[++tot].k = -s[i];
seg[tot].b = s[i] * i - c[i] + f[i];
add(1, 1, n, tot);
}
print(f[n], '\n');
return 0;
}
} // namespace XSC062
#undef int
B. 防御准备
http://222.180.160.110:1024/contest/4624/problem/2
BZOJ3156。
和上一题很像啊。总之为了避免麻烦,我们先把序列 reverse
一下。
我们令 \(f_i\) 表示在 \(i\) 处放守卫塔的最小代价。那么有:
那么答案就是 \(f_{n+1}\)。然后 \(\sum\limits_{k=j+1}^{i-1}k-j\) 是个等差数列,长度为 \((i-1)-(j+1)+1\),首、末项分别为 \(1\) 和 \(i-1-j\),则该式值为 \(\dfrac {(i-j)\times (i-j-1)}2\)。
则原式等价于:
令 \(k=-j\),\(b=\dfrac {j^2+j}2+f_j\),则 \(\min\) 内值为 \(x=i\) 时李超的最低交点。
注意题目有必须在 \(1\) 号点(reverse
后)建站的要求。
#define int long long
namespace XSC062 {
using namespace fastIO;
const int inf = 1e18;
const int maxn = 1e6 + 5;
const int maxm = 1e6 + 5;
#define lt (p << 1)
#define rt (lt | 1)
struct __ { int k, b; };
struct _ { int l, r, u; };
int n, tot;
__ seg[maxn];
_ t[maxm << 2];
int f[maxn], a[maxn];
int min(int x, int y) {
return x < y ? x : y;
}
void swap(int &x, int &y) {
x ^= y ^= x ^= y;
return;
}
void bld(int p, int l, int r) {
t[p].l = l, t[p].r = r, t[p].u = 0;
if (l == r) return;
int mid = (l + r) >> 1;
bld(lt, l, mid), bld(rt, mid + 1, r);
return;
}
int getv(int id, int x) {
if (!id) return inf;
return x * seg[id].k + seg[id].b;
}
void upd(int p, int id) {
if (!t[p].u) { t[p].u = id; return; }
int mid = (t[p].l + t[p].r) >> 1;
int v1 = getv(t[p].u, mid),
v2 = getv(id, mid);
if (v2 < v1) swap(t[p].u, id);
v1 = getv(t[p].u, t[p].l);
v2 = getv(id, t[p].l);
if (v2 < v1) upd(lt, id);
v1 = getv(t[p].u, t[p].r);
v2 = getv(id, t[p].r);
if (v2 < v1) upd(rt, id);
return;
}
void add(int p, int l, int r, int id) {
if (l <= t[p].l && t[p].r <= r) {
upd(p, id);
return;
}
int mid = (t[p].l + t[p].r) >> 1;
if (l <= mid) add(lt, l, r, id);
if (r > mid) add(rt, l, r, id);
return;
}
int ask(int p, int x) {
int res = inf;
if (t[p].u) res = getv(t[p].u, x);
if (t[p].l == t[p].r) return res;
int mid = (t[p].l + t[p].r) >> 1;
if (x <= mid) res = min(res, ask(lt, x));
else res = min(res, ask(rt, x));
return res;
}
int fun(int x) {
return x * (x - 1) / 2;
}
int main() {
read(n);
bld(1, 1, n);
for (int i = 1; i <= n; ++i)
read(a[n - i + 1]);
for (int i = 1; i <= n + 1; ++i) {
if (i != 1)
f[i] = ask(1, i) + a[i] + fun(i);
else f[i] = a[i];
seg[++tot].k = -i;
seg[tot].b = fun(i + 1) + f[i];
add(1, 1, n, tot);
}
print(f[n + 1], '\n');
return 0;
}
} // namespace XSC062
#undef int
C. 土地购买
http://222.180.160.110:1024/contest/4624/problem/3
先按长为第一关键字、高为第二关键字从大到小排个序,然后把可以被完全包含的先剃掉(因为可以将它和包含者捆绑)。
这个时候就有个很有意思的事情,就是由于长是递减的,高一定是递增的(否则就被剃掉了)。
令 \(f_i\) 表示在 \(i\) 后分隔的最小代价,则有:
显然令 \(k=W_{j+1}\) 且 \(b=f_j\),答案为李超在 \(x=H_i\) 时的最低交点。
#define int long long
namespace XSC062 {
using namespace fastIO;
const int lim = 1e6;
const int inf = 1e18;
const int maxn = 5e5 + 5;
const int maxm = 1e6 + 5;
#define lt (p << 1)
#define rt (lt | 1)
struct __ { int k, b; };
struct ___ { int h, w; };
struct _ { int l, r, u; };
___ a[maxn];
int f[maxn];
__ seg[maxn];
_ t[maxm << 2];
int n, tot, cnt;
int min(int x, int y) {
return x < y ? x : y;
}
void swap(int &x, int &y) {
x ^= y ^= x ^= y;
return;
}
void bld(int p, int l, int r) {
t[p].l = l, t[p].r = r, t[p].u = 0;
if (l == r) return;
int mid = (l + r) >> 1;
bld(lt, l, mid), bld(rt, mid + 1, r);
return;
}
int getv(int id, int x) {
if (!id) return inf;
return x * seg[id].k + seg[id].b;
}
void upd(int p, int id) {
if (!t[p].u) { t[p].u = id; return; }
int mid = (t[p].l + t[p].r) >> 1;
int v1 = getv(t[p].u, mid),
v2 = getv(id, mid);
if (v2 < v1) swap(t[p].u, id);
v1 = getv(t[p].u, t[p].l);
v2 = getv(id, t[p].l);
if (v2 < v1) upd(lt, id);
v1 = getv(t[p].u, t[p].r);
v2 = getv(id, t[p].r);
if (v2 < v1) upd(rt, id);
return;
}
void add(int p, int l, int r, int id) {
if (l <= t[p].l && t[p].r <= r) {
upd(p, id);
return;
}
int mid = (t[p].l + t[p].r) >> 1;
if (l <= mid) add(lt, l, r, id);
if (r > mid) add(rt, l, r, id);
return;
}
int ask(int p, int x) {
int res = inf;
if (t[p].u) res = getv(t[p].u, x);
if (t[p].l == t[p].r) return res;
int mid = (t[p].l + t[p].r) >> 1;
if (x <= mid) res = min(res, ask(lt, x));
else res = min(res, ask(rt, x));
return res;
}
int fun(int x) {
return x * (x - 1) / 2;
}
int main() {
read(n);
bld(1, 1, lim);
for (int i = 1; i <= n; ++i)
read(a[i].h), read(a[i].w);
std::sort(a + 1, a + n + 1,
[&](___ x, ___ y) {
return x.w == y.w ?
x.h > y.h : x.w > y.w; } );
for (int i = 1; i <= n; ++i) {
if (a[i].h > a[cnt].h)
a[++cnt] = a[i];
}
for (int i = 0; i <= cnt; ++i) {
if (i) f[i] = ask(1, a[i].h);
if (i != cnt) {
seg[++tot].k = a[i + 1].w;
seg[tot].b = f[i];
add(1, 1, lim, tot);
}
}
print(f[cnt], '\n');
return 0;
}
} // namespace XSC062
#undef int
总之就是我们发扬传统艺能,在一个晚自习用李超草过了三道斜优。🤔
—— · EOF · ——
真的什么也不剩啦 😖