P5298 [PKUWC2018]Minimax

题目链接

都到 NOI 了还不会整体 DP,所以特意学了一下。

首先我们能够轻易地想到 (n^2) DP 的做法:设 (f(p,w)) 表示点 (p) 的值为 (w) 的概率。转移:

[Q_p = 1 - P_p ]

[lsum(p,w) = sum_{i le w}f(p,i) ]

[rsum(p,w)=sum_{i ge w}f(p,i) ]

[f(p,w) gets f(rs,w)(P_plsum(ls,w) + Q_prsum(ls,w)) ]

[f(p,w) gets f(ls, w)(P_plsum(rs,w) + Q_prsum(rs,w)) ]

限制枚举范围可以优化到 (O(sum dep_p)),能通过 50pts

然后发现这个转移有共性,相当于系统地给 dp 值做一种操作。我们可以用线段树来模拟。不过这个题并不是整体DP的板子题,它有很多方便的地方,比如说我们可以拆分贡献,转化为左对右/右对左的贡献,直接转化成区间乘问题。因此我们对 (x,y) 分别记个左右的前/后缀和,到只有一个有点的时候直接打乘法标记即可。由于空节点的权值都是零,打乘法标记也没用,因此不用管如何将标记传给“空儿子”。

复杂度:(O(nlogn))

关键代码:

inline void pushtag(int cur, ll v) {
	if (!cur)	return ;
	sum[cur] = sum[cur] * v % P;
	tag[cur] = tag[cur] * v % P;
}
inline void pushdown(int cur) {
	if (tag[cur] != 1)
		pushtag(ls[cur], tag[cur]), pushtag(rs[cur], tag[cur]), tag[cur] = 1;
}
void add(int L, int R, int pos, int &cur) {
	if (!cur)	cur = ++ttot, tag[cur] = 1;
	++sum[cur];
	if (L == R)	return ;
	int mid = (L + R) >> 1;
	if (pos <= mid)	add(L, mid, pos, ls[cur]);
	else	add(mid + 1, R, pos, rs[cur]);
}
int merge(ll lx, ll rx, ll ly, ll ry, ll p, ll q, int x, int y) {
	if (!x && !y)	return 0;//Attention!!!!! &&
	pushdown(x), pushdown(y);//Attention!!!!
	if (!x) {
		pushtag(y, (p * lx % P + q * rx % P) % P);
		return y;
	}
	if (!y) {
		pushtag(x, (p * ly % P + q * ry % P) % P);
		return x;
	}
	ll nlx = lx + sum[ls[x]], nrx = rx + sum[rs[x]];
	ll nly = ly + sum[ls[y]], nry = ry + sum[rs[y]];
	nlx %= P; nrx %= P; nly %= P; nry %= P;//Attention!!!!!!
	ls[x] = merge(lx, nrx, ly, nry, p, q, ls[x], ls[y]);
	rs[x] = merge(nlx, rx, nly, ry, p, q, rs[x], rs[y]);
	sum[x] = (sum[ls[x]] + sum[rs[x]]) % P;//Attention!!!!
	return x;
}
void dfs(int cur) {
	int sz = son[cur].size();
	if (!sz) {
		add(1, htot, v[cur], rt[cur]);
	} else if (sz == 1) {
		dfs(son[cur][0]);
		rt[cur] = rt[son[cur][0]];
	} else {
		dfs(son[cur][0]), dfs(son[cur][1]);
		rt[cur] = merge(0, 0, 0, 0, p[cur], q[cur], rt[son[cur][0]], rt[son[cur][1]]);
	}
}
原文地址:https://www.cnblogs.com/JiaZP/p/13532057.html