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]]);
}
}