P5044 [IOI2018] meetings 会议
写完了。感谢 ywy 学长的讲解!
显然选最大值不优,那么我们可以根据最大值将区间分成两半,分别考虑地点在左右两边的情况,取个最小值即可。用式子来表示就是:
\[f(l,r)=\min(\sum_{i=l}^th_t+f(t+1,r),\sum_{i=t}^rh_t+f(l,t-1))
\]
我们考虑在最值那里计算答案。但是左右端点都不固定不好做,我们可以根据上面那个式子把询问拆成两部分,我们只考虑求右子树内部分的 \(f\),左子树的 \(f\) 可以通过翻转得到。这一步操作的作用是让区间在笛卡尔树上的部分的左边是完整的。同时方便计算答案。
我们现在要维护 \(f_{p,r}\),表示以当前子树根节点 \(p\) 为根,从最左边到 \(r\) 的答案。转移直接用上面的那个式子。不过那个 \(\min\) 很不好搞,考虑怎么把它去掉。发现随着 \(r\) 的增大,右边部分的增量一直是 \(h_t\),而左边部分的增量不会超过 \(h_t\),因此按照这种趋势右边会慢慢超过左边。因此,直接二分即可。
去掉 \(\min\) 以后就很好搞了,发现左边就是在原数组上区间加,右边就是区间赋值成等差数列。用线段树合并做即可。然后又发现实际上左右子树的 DP 数组是不交的,因此直接在一个线段树上搞即可。
不过这样是 \(O(nlog^2n)\) 的,线段树上二分则是 \(O(nlogn)\)。比较考察对线段树掌握的熟练程度。
线段树维护区间加,区间赋值为等差数列,单点查询,线段树上二分:
namespace SGT {
int ls[NN], rs[NN], root, ttot, len[NN];
ll val[NN], taga[NN], tagk[NN], tagp[NN];//val : right point's value
inline void clear() {
memset(val, 0, sizeof(val));
memset(tagp, 0, sizeof(tagp));
for (register int i = 1; i <= ttot; ++i) taga[i] = tagk[i] = Rand;
}
inline void pushup(int cur) {
val[cur] = val[rs[cur]];
}
inline void pushtag(int cur, ll k, ll b) {
if (!cur) return ;
tagp[cur] = 0;
if (len[cur] == 1) return val[cur] = k + b, void();
taga[cur] = b; tagk[cur] = k;
val[cur] = k * len[cur] + b;
}
inline void pushplus(int cur, ll x) {
if (!cur) return ;
val[cur] += x;
if (taga[cur] != Rand) return taga[cur] += x, void();//Attention!!
tagp[cur] += x;
}
inline void pushdown(int cur) {
if (taga[cur] != Rand || tagk[cur] != Rand) {
pushtag(ls[cur], tagk[cur], taga[cur]);
pushtag(rs[cur], tagk[cur], taga[cur] + tagk[cur] * len[ls[cur]]);
taga[cur] = tagk[cur] = Rand;
}
if (tagp[cur]) pushplus(ls[cur], tagp[cur]), pushplus(rs[cur], tagp[cur]), tagp[cur] = 0;
}
void build(int L, int R, int &cur) {
cur = ++ttot;
ls[cur] = rs[cur] = 0;
len[cur] = R - L + 1; val[cur] = 0; taga[cur] = tagk[cur] = Rand;
if (L == R) return ;
int mid = (L + R) >> 1;
build(L, mid, ls[cur]), build(mid + 1, R, rs[cur]);
}
void modify(int L, int R, int l, int r, ll k, ll b, int cur) {
if (l <= L && R <= r) { pushtag(cur, k, b); return ; }
pushdown(cur);
int mid = (L + R) >> 1;
if (l <= mid && r > mid) {//Attention!!!!!!!!!!
modify(L, mid, l, r, k, b, ls[cur]);
modify(mid + 1, R, l, r, k, b + 1ll * (mid - max(l, L) + 1) * k, rs[cur]);
} else {
if (l <= mid) modify(L, mid, l, r, k, b, ls[cur]);
if (r > mid) modify(mid + 1, R, l, r, k, b, rs[cur]);//Attention!!!!!!!!!
}
pushup(cur);
}
inline void Modify(int l, int r, ll k, ll b) { if (l <= r) modify(1, n, l, r, k, b, root); }
inline void add(int L, int R, int l, int r, ll x, int cur) {
if (l <= L && R <= r) { pushplus(cur, x); return ;}
pushdown(cur);
int mid = (L + R) >> 1;
if (l <= mid) add(L, mid, l, r, x, ls[cur]);
if (r > mid) add(mid + 1, R, l, r, x, rs[cur]);
pushup(cur);
}
inline void Add(int l, int r, ll x) { if (l <= r) add(1, n, l, r, x, root); }
ll query(int L, int R, int pos, int cur) {
if (L == R) return val[cur];
pushdown(cur);
int mid = (L + R) >> 1;
if (pos <= mid) return query(L, mid, pos, ls[cur]);
return query(mid + 1, R, pos, rs[cur]);
}
inline ll Query(int p) { return query(1, n, p, root); }
int dfs(int L, int R, int l, int r, ll k, ll b, int cur) {//线段树上二分
if (len[cur] == 1) return val[cur] >= k + b ? L : L - 1;
pushdown(cur);
int mid = (L + R) >> 1;
if (l <= L && R <= r) {//Attention!!!
if (val[ls[cur]] >= k * len[ls[cur]] + b)
return dfs(mid + 1, R, l, r, k, k * len[ls[cur]] + b, rs[cur]);
return dfs(L, mid, l, r, k, b, ls[cur]);
}
if (l <= mid && r > mid) {//Attention!!!!!
if (val[ls[cur]] >= k * (mid - max(l,L) + 1) + b)//Attention!!!!!
return dfs(mid + 1, R, l, r, k, k * (mid - max(l, L) + 1) + b, rs[cur]);
return dfs(L, mid, l, r, k, b, ls[cur]);
}
if (l <= mid) return dfs(L, mid, l, r, k, b, ls[cur]);
return dfs(mid + 1, R, l, r, k, b, rs[cur]);
}
inline int Dfs(int l, int r, ll k, ll b) { return dfs(1, n, l, r, k, b, root); }
}
笛卡尔树的构建及笛卡尔树上 DP 回答询问:
struct Queries {
int l, r, t;
ll res, ans;
Queries() { l = r = t = 0; res = 0; ans = inf; }//Attention!!!
inline void calc() {
ans = min(ans, res + 1ll * h[t] * (t - l + 1));
}
inline void cg() {
l = n - l + 1, r = n - r + 1;
swap(l, r);
res = 0;
}
}qs[N];
vector<int> vec[N];
int ls[N], rs[N], stk[N], stop, Rt, tl[N], tr[N];
int dep[N], siz[N], fa[N], son[N];
inline void Clear()
void dfs_son(int cur, int faa)
int top[N];
void dfs_chain(int cur, int topp)
inline int get_lca(int x, int y)
inline void init() {
for (register int i = 1; i <= n; ++i) {
while (stop && h[i] > h[stk[stop]]) ls[i] = stk[stop], --stop;
if (stop) rs[stk[stop]] = i;
stk[++stop] = i;
}
Rt = stk[1];
dfs_son(Rt, 0);
dfs_chain(Rt, Rt);
}
void dfs(int cur) {
if (!cur) return ;
dfs(ls[cur]); dfs(rs[cur]);
if (!ls[cur] && !rs[cur]) {
SGT::Modify(cur, cur, 0, h[cur]);
} else if (!ls[cur]) {
SGT::Add(cur, tr[cur], h[cur]);
} else if (!rs[cur]) {
SGT::Modify(cur, cur, 0, SGT::Query(cur - 1) + h[cur]);//Attention!!!!!!
} else {
ll tmp = SGT::Query(cur - 1);
int pos = SGT::Dfs(cur + 1, tr[cur], h[cur], tmp - 1ll * h[cur] * (cur - tl[cur]));
SGT::Modify(cur, pos, h[cur], tmp);
SGT::Add(pos + 1, tr[cur], 1ll * (cur - tl[cur] + 1) * h[cur]);//Attention!!!!
}
for (register unsigned int i = 0; i < vec[cur].size(); ++i) {//Attention!!!!!
int nw = vec[cur][i];
int r = qs[nw].r;
qs[nw].res = SGT::Query(r);
}
vec[cur].clear();
}