qoj 18328 假想对冲
qoj18328 假想对冲
考虑设 dp: \(f(u,d)\) 表示点 \(u\) 深度 \(+d\) 的所有儿子对答案的贡献。
记 \(g(u,d)=\min(w_u,f(u,d))\),有转移:\(f(u,d\ge 1)=\sum_v g(v,d-1)\)。
对于深度相关的 dp 可以考虑长剖。一个经典的长剖优化 dp 可以将 \(f(u,d)=\sum f(v,d)\) 类的 dp 优化至 \(\mathcal O(n)\)。具体地令 \(u\) 继承 \(son_u\) 的 dp 数组,然后合并所有轻儿子,每个点只会在链顶处被合并 \(1\) 次。
但是这里我们需要维护 \(g\) 是 \(f\) 对 \(w_u\) 取 \(\min\)。考虑一个类似 slope trick 的“削平”过程,会得到若干连续值相同的段。
尝试维护连续段,发现每个点除了这轮要被删去(削平)的段之外只会遍历常数段。而总共只会删去 \(O(n)\) 次。时间复杂度 \(\mathcal O(n)\)。
实现的时候可以用类似链表然后归并的方式(见代码)可以证明访问到的节点数是 \(\mathcal O(\min(|A|,|B|))\) 的。
当然这么实现之后就不需要预留最深长度的 dp 数组了,所以就不需要长剖了()
#include <iostream>
const int N = 1e6 + 7;
typedef long long i64;
i64 ans[N];
int n, f[N], w[N], q[N];
struct node { i64 v; int l, to; } gt[N];
inline void solve() {
std::cin >> n;
for(int i = 2; i <= n; ++i) std::cin >> f[i];
for(int i = 2; i <= n; ++i) std::cin >> w[i];
auto merge = [](int x, int y) {
if(!x || !y) return x | y;
int i = 0, j = 0;
while(x && y) {
if(gt[x].l > gt[y].l) std::swap(x, y);
gt[x].v += gt[y].v, i = (i ? (gt[i].to = x, i) : j) = x;
if(!(gt[y].l -= gt[x].l)) y = gt[y].to; x = gt[x].to;
}
return gt[i].to = x | y, j;
};
for(int i = 1; i <= n; ++i) ans[i] = q[i] = 0;
for(int u = n; u > 1; --u) {
int p = q[u], l = 0; i64 lmt = ans[u];
while(p && gt[p].v >= w[u]) l += gt[p].l, lmt -= 1ull * gt[p].v * gt[p].l, p = gt[p].to;
gt[u] = { w[u], l+1, p }, lmt += 1ll * (l + 1) * w[u], q[f[u]] = merge(q[f[u]], u), ans[f[u]] += lmt;
}
for(int i = 1; i <= n; ++i)
std::cout << ans[i] << " ";
std::cout << "\n";
}
int main() {
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int t; std::cin >> t; while(t--) solve();
}
本文来自博客园,作者:CuteNess,转载请注明原文链接:https://www.cnblogs.com/CuteNess/p/22879308

浙公网安备 33010602011771号