P7897 [Ynoi2006] spxmcq
有一个暴力 dp,\(dp_u\) 表示以 \(u\) 为根节点最大权值总和,则有 \(dp_u=\sum\limits_{v\in son}\max(dp_v,0)\)。
这东西显然没法直接维护,自然地想到建一棵转移树。具体地讲,当且仅当 \(u\) 的儿子 \(v\) 满足 \(dp_v>0\) 时,才在 \(u,v\) 间连边。其实应该叫转移森林,因为显然不连通。
不难想到 \(x\) 越大转移森林边数越多,所以我们离线下来按 \(x\) 排个序,转移森林就只会加边不会删边。
这个时候要求的就是随着 \(x\) 的增加哪些边新加入了,也就是哪些 \(dp_v\) 大于 \(0\) 了。直接想非常复杂,因为 \(dp_v\) 会受到它的儿子的影响,它的儿子又会受到它的孙子的影响……
但是最终,一定会有一个 \(dp_v\) 最先大于 \(0\),它是导火索,随着他变得 \(>0\),其它的 \(dp\) 值也随之变化。
我们设当前 \(v\) 在转移森林中子节点个数为 \(sze_v\),当前在转移森林中子节点权值和为 \(sum_v\)。那么对于 \(v\),如果它是那个导火索,我们可以发现它对于给定的 \(x\),\(dp\) 值为 \(sze_v\cdot x+sum_v\)。要想使得这个 \(sze_v\times x+sum_v>0\),解不等式得到 \(x\ge \lceil\frac{-sum_v}{sze_v}\rceil\)。
把所有的 \(\lceil\frac{-sum_v}{sze_v}\rceil\) 扔到一个堆里去,每次取出最小元素看是否小于等于当前的 \(x\),如果小于等于就修改。
修改是这样的:(虚线表原树边,实线表转移森林边)
所以修改时,我们需要更新当前某个点到根节点路径上所有的 \(sum\) 与 \(sze\)。
然后查询时,我们需要查询当前点的 \(dp\) 值,即 \(sze\cdot x+sum\),也就是查单点的 \(sze,sum\)。
由于这个森林会不断加边,总结以下就是我们需要维护一个数据结构维护一个森林,支持:
- 加边。
- 将某个点到根的路径修改。
- 单点查值。
转移森林中某个点到根的路径在原树中就是一条链,所以其实我们可以忽视这个动态加边,只需用并查集维护根节点即可。
对于 \(2,3\) 操作,树剖肯定超时,可以差分。如果把 \(u\) 到它的子节点 \(v\) 的所有点权 \(+\Delta\),那么在 \(u\) 的父亲处 \(-\Delta\),在 \(v\) 处加 \(\Delta\)。查询时,只需查询这个点子树内所有点权和。这样就可以 dfs 序+树状数组维护。
然后就注意向上取整的细节,整数默认向 \(0\) 取整,所以要想好到底是在向上还是向下取整。直接 ceil 是可以冲过去的,如果手写 ceil 需要判正负。
#include <cstdio>
#include <queue>
#include <algorithm>
#include <cmath>
#define int long long
typedef std::pair<int, int> PLI;
struct Edge {int to, nxt;} e[1000005];
int f[1000005], head[1000005], In[1000005], Out[1000005], fa[1000005], tot, cnt, n, m, ans[1000005];
bool del[1000005];
std::priority_queue<PLI, std::vector<PLI>, std::greater<PLI> > Q;
struct Quest {
int u, x, id;
inline bool operator < (const Quest a) const {return x < a.x;}
} q[1000005];
inline void AddEdge(int u, int v) {
e[++ tot].to = v, e[tot].nxt = head[u], head[u] = tot;
}
void dfs(int u) {
In[u] = ++ cnt;
for (int i = head[u]; i; i = e[i].nxt) dfs(e[i].to);
Out[u] = cnt;
}
int find(int x) {return fa[x] == x ? x : fa[x] = find(fa[x]);}
struct BIT {
int c[1000005];
inline void update(int x, int d) {
for (int i = x; i <= n; i += (i & ~i + 1)) c[i] += d;
}
inline int prefixsum(int x) {
int sum = 0;
for (int i = x; i; i &= i - 1) sum += c[i];
return sum;
}
inline int query(int l, int r) {return prefixsum(r) - prefixsum(l - 1);}
} sum, sze;
void change(int u, int x) {
while (u != 1) {
del[u] = true;
int v = find(f[u]);
int sumu = sum.query(In[u], Out[u]), szeu = sze.query(In[u], Out[u]);
sum.update(In[f[u]], sumu), sze.update(In[f[u]], szeu);
if (f[v]) sum.update(In[f[v]], -sumu), sze.update(In[f[v]], -szeu);
fa[u] = v;
int sumv = sum.query(In[v], Out[v]), szev = sze.query(In[v], Out[v]);
if (szev * x + sumv <= 0) {
Q.push(std::make_pair(sumv > 0 ? -sumv / szev : (-sumv + szev - 1) / szev, v));
break;
}
u = v;
}
}
signed main() {
scanf("%lld%lld", &n, &m);
for (int i = 1; i <= n; ++ i) fa[i] = i;
for (int i = 2; i <= n; ++ i) scanf("%lld", f + i), AddEdge(f[i], i);
dfs(1), del[1] = true;
for (int i = 1, x; i <= n; ++ i) {
scanf("%lld", &x);
Q.push(std::make_pair(-x, i));
sum.update(In[i], x), sze.update(In[i], 1);
if (i != 1) sum.update(In[f[i]], -x), sze.update(In[f[i]], -1);
}
for (int i = 1; i <= m; ++ i) scanf("%lld%lld", &q[i].u, &q[i].x), q[i].id = i;
std::sort(q + 1, q + m + 1);
for (int i = 1; i <= m; ++ i) {
while (Q.size() && Q.top().first <= q[i].x) {
int u = Q.top().second;
Q.pop();
if (del[u]) continue;
change(u, q[i].x);
}
ans[q[i].id] = 1ll * sze.query(In[q[i].u], Out[q[i].u]) * q[i].x + sum.query(In[q[i].u], Out[q[i].u]);
}
for (int i = 1; i <= m; ++ i) printf("%lld\n", ans[i]);
return 0;
}