LOJ2537:「PKUWC2018」Minimax

本文探讨了一道复杂的树形DP题目,通过离散化权值和使用启发式合并策略,利用平衡树或动态开点线段树解决了一个关于二叉树节点权值概率的问题。文章详细解释了如何维护节点取值概率,并提供了完整的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

LOJ

题意

给了你一棵二叉树,一号节点时根节点,每个叶子节点都有一个权值,非叶子节点的权值有 P P P的概率是它子节点的最大值,有 1 − P 1-P 1P的几率是它子节点的最小值,求一号节点取所有权值的概率,叶子节点的权值保证不同,且 P P P换算成分数后 0 &lt; P &lt; 1 0&lt;P&lt;1 0<P<1,可以证明一号节点能取完所有叶子节点的权值;

题解

看起来非常像树形DP,尝试推一下转移式子,先离散化权值,定义 F [ u ] [ i ] F[u][i] F[u][i]表示 u u u节点取权值 i i i的概率;叶子节点肯定只有一个 i i i,且概率为 1 1 1;考虑怎么转移,如果只有一个儿子,父亲的suoy取值概率肯定直接等于儿子的所有取值概率;现在有两个儿子,如果权值 i i i来自左儿子 s o n [ L ] son[L] son[L],那么 F [ u ] [ i ] = F [ s o n [ L ] [ i ] ∗ ( ∑ j , j &lt; i F [ s o n [ R ] ] [ j ] ∗ P u + ∑ j , j &gt; i F [ s o n [ R ] ] [ j ] ∗ ( 1 − P u ) ) F[u][i]=F[son[L][i]*(\sum_{j,j&lt;i}F[son[R]][j]*P_u+\sum_{j,j&gt;i}F[son[R]][j]*(1-P_u)) F[u][i]=F[son[L][i](j,j<iF[son[R]][j]Pu+j,j>iF[son[R]][j](1Pu))
从右儿子的转移同理,现在考虑要维护哪些东西才能维护这个式子,首先肯定要知道每个点有哪些取值及这些取值的概率,能快速求出兄弟节点中所有小于和大于某权值的和,想到平衡树或者动态开点线段树;但是怎么维护转移呢,考虑启发式合并,在启发式合并时记录兄弟节点中大于某段区间的概率和(小于的用 1 1 1减去大于的就能得到了),那么在启发式合并的最后取节点时再把贡献算进去,然后标记下传就行了;

#include<bits/stdc++.h>
#define Fst first
#define Snd second
#define RG register
#define mp make_pair
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long LL;
typedef long double LD;
typedef unsigned int UI;
typedef unsigned long long ULL;
template<typename T> inline void read(T& x) {
	char c = getchar();
	bool f = false;
	for (x = 0; !isdigit(c); c = getchar()) {
		if (c == '-') {
			f = true;
		}
	}
	for (; isdigit(c); c = getchar()) {
		x = x * 10 + c - '0';
	}
	if (f) {
		x = -x;
	}
}
template<typename T, typename... U> inline void read(T& x, U& ... y) {
	read(x), read(y...);
}
const int N=3e5+10,P=998244353;
int n,p,cnt,size;
int G[N],V[N],head[N],root[N];
bool YES[N];
map<int,int> S;
struct Node {
	int sum,lazy;
	int lo,ro;
}Tr[N*20];
#define L Tr[o].lo
#define R Tr[o].ro
struct SegmentTree {
	void Modify(int l,int r,int &o,int pos) {
		o=++size;
		Tr[o].sum=1; Tr[o].lazy=1;
		if(l==r) return;
		int mid=l+r>>1;
		if(pos<=mid) Modify(l,mid,L,pos);
		else Modify(mid+1,r,R,pos);
	}
	void Down(int o,int val) {
		if(!o) return;
		Tr[o].sum=1ll*Tr[o].sum*val%P;
		Tr[o].lazy=1ll*Tr[o].lazy*val%P;
	}
	void Pushdown(int o) {
		if(Tr[o].lazy!=1) {
			Down(L,Tr[o].lazy);
			Down(R,Tr[o].lazy);
			Tr[o].lazy=1;
		}
	}
	void Merge(int l,int r,int &x,int y,int a,int b,int p) {
		if(!x||!y) {
			if(!x) Down(y,(1ll*(1-b+P)%P*p%P+1ll*(1-p+P)%P*b%P)%P);
			else Down(x,(1ll*(1-a+P)%P*p%P+1ll*(1-p+P)%P*a%P)%P);
			x=x+y;
			return;
		}
		Pushdown(x); Pushdown(y);
		int mid=l+r>>1;
		Merge(l,mid,Tr[x].lo,Tr[y].lo,(a+Tr[Tr[y].ro].sum)%P,(b+Tr[Tr[x].ro].sum)%P,p); Merge(mid+1,r,Tr[x].ro,Tr[y].ro,a,b,p);
		Tr[x].sum=Tr[Tr[x].lo].sum+Tr[Tr[x].ro].sum;
		if(Tr[x].sum>=P) Tr[x].sum-=P;
	}
	int Query(int l,int r,int o,int pos) {
		if(l==r) return Tr[o].sum;
		Pushdown(o);
		int mid=l+r>>1,t;
		if(pos<=mid) t=Query(l,mid,L,pos);
		else t=Query(mid+1,r,R,pos);
		Tr[o].sum=Tr[L].sum+Tr[R].sum;
		if(Tr[o].sum>=P) Tr[o].sum-=P;
		return t;
	}
}SgT;
struct Edge {
	int to,last;
	Edge () {}
	Edge (int a,int b) :to(a),last(b) {}
}edge[N];
void ADD(int a,int b) {
	edge[++p]=Edge(b,head[a]); head[a]=p;
}
void DFS(int u) {
	if(!YES[u]) {
		SgT.Modify(1,cnt,root[u],S[G[u]]);
		return;
	}
	for(int i=head[u];i;i=edge[i].last) {
		int v=edge[i].to;
		DFS(v);
		if(!root[u]) root[u]=root[v];
		else SgT.Merge(1,cnt,root[u],root[v],0,0,G[u]);
	}
}
int Pow(int a,int k) {
	int res=1;
	for(int i=k;i;i>>=1) {
		if(i&1) res=1ll*res*a%P;
		a=1ll*a*a%P;
	}
	return res;
}
int main() {
#ifdef rua
	freopen("GG.in","r",stdin);
#endif
	read(n);
	for(int i=1;i<=n;++i) {
		int fa; read(fa);
		if(fa) ADD(fa,i),YES[fa]=true;
	}
	int Inv=Pow(10000,P-2);
	for(int i=1;i<=n;++i) {
		read(G[i]);
		if(!YES[i]) V[++cnt]=G[i];
		else G[i]=1ll*G[i]*Inv%P;
	}
	sort(V+1,V+cnt+1);
	for(int i=1;i<=cnt;++i) S[V[i]]=i;
	DFS(1);
	int res=0;
	for(int i=1;i<=cnt;++i) {
		int t=SgT.Query(1,cnt,root[1],i);
		(res+=1ll*i*V[i]%P*t%P*t%P)%=P;
	}
	printf("%d\n",res);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值