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\)

由于这个森林会不断加边,总结以下就是我们需要维护一个数据结构维护一个森林,支持:

  1. 加边。
  2. 将某个点到根的路径修改。
  3. 单点查值。

转移森林中某个点到根的路径在原树中就是一条链,所以其实我们可以忽视这个动态加边,只需用并查集维护根节点即可。

对于 \(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;
}
posted @ 2022-01-21 22:08  zqs2020  阅读(60)  评论(0编辑  收藏  举报