DP方程十分简单,考虑前对后贡献即可。
\(f_i = \min_{l_i \leq j < i} \left\{ f_j + \left(\max_{j < k \leq i} \left\{t_k\right\}\right) \times \left(\sum_{k=i+1}^n w_k \right)\right\}\)
\(O\left(n^2\right)\) 显然。\(w\) 后缀和递减,\(\max t\) 向左单调增,显然单调性优化。
使用单调栈将 \(\max t\) 分成几段,然后查询需要维护区间凸壳。因为单调栈需要支持末尾插入,末尾删除,区间查询凸壳,十分麻烦。
做法很多,学了一下姿势。(当然不会去写动态凸包的啊)
对于一般的支持末尾插入删除区间询问凸壳的问题,有如下做法:
(因为很多时候题目性质不同,就不仔细分析复杂度了)
树形法
把单调栈变成树形结构,弹出是跳fa,插入是跳儿子,那么查询是树上的链。
点分治
把询问拆成经过重心的两条链,分别在各自子树中查询,并且启发式凸包合并。
但是这里貌似是有根树分治,那么直接上面的链建单调队列,贡献到下面就好了。
树剖
直接树剖,对于每条链用线段树维护。
平衡树
考虑插入节点时删除的节点是一个区间,那么平衡树二分这个区间,删除的时候绑定到新节点上。
这样在新节点被删除的时候,把原区间归还即可。
二进制分组
延迟重构 normal trick?
用线段树维护二进制分组很方便,删除时到根打标记,保持每个组仅有右链有tag。
查询时如果有tag就递归俩儿子。
如果插入时可以合并组,如果子树有标记,递归重构即可。
势能分析一波,删除重构不会太多。
时间戳线段树
不要删除,直接区间放点好了。
我写了发二进制分组。合并复杂度线性,查询因为横坐标单调,所以使用单调队列。时空一个log。
画图时算叉积把方向画错了,导致打错个符号,拍巨久才拍出来……/px
#include <bits/stdc++.h>
const int MAXN = 100010;
typedef long long LL;
typedef long double LD;
const LL INFL = 0x3f3f3f3f3f3f3f3fLL;
typedef std::vector<int> VI;
void getmin(LL & x, LL y) { x > y ? x = y : 0; }
int n, ls[MAXN], ts[MAXN], ws[MAXN];
LL suc[MAXN], dp[MAXN];
int st[MAXN], top, vs[MAXN];
namespace ch {
int tag[MAXN << 2], at[MAXN], sz[MAXN << 2];
void build(int u, int l, int r) {
sz[u] = r - l + 1;
if (l == r) return (void) (at[l] = u);
int mid = l + r >> 1;
build(u << 1, l, mid); build(u << 1 | 1, mid + 1, r);
}
LL ks[MAXN], bs[MAXN]; int bak;
bool chk(int a, int b, LL at) {
return (LD) ks[a] * at + bs[a] >= (LD) ks[b] * at + bs[b];
}
bool cross(LL x1, LL y1, LL x2, LL y2) {
return (LD) x1 * y2 - (LD) x2 * y1 >= -1e-10;
}
bool cmp(int a, int b, int c) {
return cross(ks[b] - ks[a], bs[b] - bs[a], ks[c] - ks[a], bs[c] - bs[a]);
}
struct monoq {
VI que; int b, e;
void set(int at) { que.clear(); que.push_back(at); b = e = 0; }
void merge(const monoq & a, const monoq & b) {
int ta = a.b, tb = b.b;
static int st[MAXN], top, now; top = 0;
for (int T = a.e - ta + b.e - tb + 2; T; --T) {
if (ta <= a.e && (tb > b.e || ks[a.que[ta]] >= ks[b.que[tb]]))
now = a.que[ta++];
else now = b.que[tb++];
while (top > 1 && cmp(st[top - 1], st[top], now)) --top;
st[++top] = now;
}
que.assign(st + 1, st + top + 1);
this -> b = 0, e = top - 1;
}
LL qry(LL at) {
while (e - b + 1 >= 2 && chk(que[b], que[b + 1], at)) ++b;
return ks[que[b]] * at + bs[que[b]];
}
} tree[MAXN << 2];
LL exqry(int u, LL at) {
if (!tag[u])
return std::min(exqry(u << 1, at), exqry(u << 1 | 1, at));
return tree[u].qry(at);
}
LL query(int u, int l, int r, int L, int R, LL at) {
if (L <= l && r <= R) return exqry(u, at);
int mid = l + r >> 1; LL res = INFL;
if (L <= mid) res = query(u << 1, l, mid, L, R, at);
if (mid < R) res = std::min(res, query(u << 1 | 1, mid + 1, r, L, R, at));
return res;
}
LL qry(LL at, int L) { return query(1, 1, n, L, bak, at); }
void rebuild(int u) {
if (tag[u]) return ;
if (!sz[u << 1] && !sz[u << 1 | 1]) {
rebuild(u << 1); rebuild(u << 1 | 1);
tag[u] = true;
tree[u].merge(tree[u << 1], tree[u << 1 | 1]);
}
}
void push_back(LL k, LL b) {
int u = at[++bak];
ks[bak] = k, bs[bak] = b;
tree[u].set(bak); tag[u] = true;
while (u) {
--sz[u], u >>= 1;
if (u) rebuild(u);
}
}
void pop_back() {
int u = at[bak--];
while (u) ++sz[u], tag[u] = false, u >>= 1;
}
}
struct segmenttree {
LL tree[MAXN << 2];
void mdf(int u, int l, int r, int tar, LL v) {
if (l == r) return (void) (tree[u] = v);
int mid = l + r >> 1;
if (tar <= mid) mdf(u << 1, l, mid, tar, v);
else mdf(u << 1 | 1, mid + 1, r, tar, v);
tree[u] = std::min(tree[u << 1], tree[u << 1 | 1]);
}
LL qry(int u, int l, int r, int L, int R) {
if (L <= l && r <= R) return tree[u];
int mid = l + r >> 1; LL res = INFL;
if (L <= mid) res = qry(u << 1, l, mid, L, R);
if (mid < R) res = std::min(res, qry(u << 1 | 1, mid + 1, r, L, R));
return res;
}
LL qry(int l, int r) { return l > r ? INFL : qry(1, 0, n, l, r); }
} seg;
int main() {
std::ios_base::sync_with_stdio(false), std::cin.tie(0);
std::cin >> n;
ch::build(1, 1, n);
for (int i = 1; i <= n; ++i)
std::cin >> ls[i] >> ts[i] >> ws[i];
for (int i = n; i; --i) suc[i] = suc[i + 1] + ws[i];
memset(dp, 0x3f, n + 1 << 3);
seg.mdf(1, 0, n, 0, dp[0] = 0);
for (int i = 1; i <= n; ++i) {
while (top && vs[top] <= ts[i])
--top, ch::pop_back();
int l = st[top] + 1;
st[++top] = i, vs[top] = ts[i];
ch::push_back(ts[i], seg.qry(l - 1, i - 1));
int tl = std::lower_bound(st + 1, st + 1 + top, ls[i]) - st;
LL t = seg.qry(ls[i], st[tl] - 1);
getmin(dp[i], t + suc[i + 1] * vs[tl]);
if (tl < top) getmin(dp[i], ch::qry(suc[i + 1], tl + 1));
seg.mdf(1, 0, n, i, dp[i]);
}
std::cout << dp[n] << std::endl;
return 0;
}