[PKUWC2018]Minimax
题意:
给 个结点的二叉树,叶子结点有互不相同的权值 , 非叶子节点有概率 取两个子节点最大值,否则取最小值。
求 , 即 种可能的结果中,第 小的权值 * * 根节点是 的概率的平方。
树形dp
由于权值互不相同,所有的可能个数就是叶子节点的个数,只要求出根结点是出现某个权值的概率, 问题就可以解决了。
因为需要比较大小,得确定当前节点的权值。
方程
可以设出方程 , 表示当前节点 的权值是 第 大的权值 的概率。
初始状态
对于每个叶子节点 ,这个点是 的概率肯定是 。
因此: 。
转移
分类讨论当前非叶子节点 的权值 从哪里来,令 是第 大的权值。
-
如果只有一个儿子,那就一定是从这个儿子来:
-
考虑从左节点来,有最大值和最小值两种途径。
- 最大值:
- 最小值:
- 最大值:
对于右节点同理。
就有:
化简:
分析
时间复杂度 , 空间复杂度 。
对于空间,只有根节点才有用,没必要开那么大,可以用到类似滚动数组的玩意,但要在树上?
对于时间,用前后缀和优化的转移是 的,还是不够好?
线段树合并
对于上述问题
线段树合并的空间是 的,因为加入新点的空间花费是 , 最多插入 次。
转移的形式像对应点相加,在乘一个值 , 简单表示为
而这个 可以在合并的时候顺路求出, 只用维护区间和就能做到。
如果遇到某个儿子的结点为空,其中的 或 , 那么打一个乘法的懒标记即可。
线段树合并时间复杂度均摊是 的。
代码
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int MAXN = 300010;
const int INF = 0x7fffffff;
const int mod = 998244353;
template <typename T>
void Read(T &x) {
x = 0; T f = 1; char a = getchar();
for(; a < '0' || '9' < a; a = getchar()) if (a == '-') f = -f;
for(; '0' <= a && a <= '9'; a = getchar()) x = (x * 10) + (a ^ 48);
x *= f;
}
int add(int a, int b) {
int c = a + b;
if (c >= mod) c -= mod;
if (c < 0) c += mod;
return c;
}
int mul(int a, int b) {
return 1ll * a * b % mod;
}
int qpow(int a, int b) {
int sum(1);
while(b) {
if (b & 1) sum = mul(sum, a);
a = mul(a, a);
b >>= 1;
}
return sum;
}
int n;
vector<int> e[MAXN];
int val[MAXN];
int cnt;
int L[MAXN << 5], R[MAXN << 5], Mul[MAXN << 5], sum[MAXN << 5];
int newnode() {
int rt = ++ cnt;
L[rt] = R[rt] = sum[rt] = 0;
Mul[rt] = 1;
return rt;
}
void pushup(int rt) {
sum[rt] = add(sum[L[rt]], sum[R[rt]]);
}
void _mul(int rt, int val) {
if (!rt) return ;
sum[rt] = mul(sum[rt], val);
Mul[rt] = mul(Mul[rt], val);
}
void pushdown(int rt) {
if (!rt) return ;
if (L[rt]) _mul(L[rt], Mul[rt]);
if (R[rt]) _mul(R[rt], Mul[rt]);
Mul[rt] = 1;
}
int update(int _L, int _C, int l, int r, int rt) {
if (!rt) rt = newnode();
if (l == r) {
sum[rt] = _C;
return rt;
}
pushdown(rt);
int m = (l + r) >> 1;
if (_L <= m) L[rt] = update(_L, _C, l, m, L[rt]);
else R[rt]= update(_L, _C, m + 1, r, R[rt]);
pushup(rt);
return rt;
}
int merge(int x, int y, int sum1, int sum2, int p, int l, int r) {
if (!x && !y) return 0;
if (!x) {
_mul(y, sum2);
return y;
}
if (!y) {
_mul(x, sum1);
return x;
}
pushdown(x), pushdown(y);
int m = (l + r) >> 1;
int ls1 = sum[L[x]], ls2 = sum[L[y]], rs1 = sum[R[x]], rs2 = sum[R[y]];
L[x] = merge(L[x], L[y], add(sum1, mul(add(1, -p), rs2)), add(sum2, mul(add(1, -p), rs1)), p, l, m);
R[x] = merge(R[x], R[y], add(sum1, mul(p, ls2)), add(sum2, mul(p, ls1)), p, m + 1, r);
pushup(x);
return x;
}
int len;
int b[MAXN];
int root[MAXN];
void dfs(int u) {
if (!e[u].size()) {
root[u] = update(lower_bound(b + 1, b + len + 1, val[u]) - b, 1, 1, len, root[u]);
} else if(e[u].size() == 1) {
int son = e[u][0];
dfs(son);
root[u] = root[son];
} else {
int l = e[u][0], r = e[u][1];
dfs(l), dfs(r);
root[u] = merge(root[l], root[r], 0, 0, val[u], 1, len);
}
}
int query(int l, int r, int rt) {
if (!rt) return 0;
if (l == r) {
return mul(mul(l, b[l]), qpow(sum[rt], 2));
}
int m = (l + r) >> 1;
pushdown(rt);
return add(query(l, m, L[rt]), query(m + 1, r, R[rt]));
}
int main() {
Read(n);
for (int i = 1; i <= n; i ++) {
int fa;
Read(fa);
e[fa].emplace_back(i);
}
for (int i = 1; i <= n; i ++) {
Read(val[i]);
if (e[i].size())
val[i] = mul(val[i], qpow(10000, mod - 2));
else
b[++ len] = val[i];
}
sort(b + 1, b + len + 1);
len = unique(b + 1, b + len + 1) - b - 1;
dfs(1);
cout << query(1, len, root[1]);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?