P5298 [PKUWC2018] Minimax - 线段树合并

题意

给定一棵 n 个点的有根树,根为 1,且每个点的儿子个数不超过 2。每个点都有一个权值,对于点 u,它有 pu 的概率使得权值为它的儿子的权值 max;有 1pu 的概率使得权值为它的儿子的权值 min。若点 u 没有儿子,那么会给定一个权值 wu

vi 为根节点能取到的第 i 小的权值,pi 为取到它的概率。你要求出 i=1ivipi2mod998244353

1n3×105,0<pu<1,1wu109

题解

因为 0<pu<1,所以根节点能取到所有出现过的权值。将所有叶子节点的权值离散化,设 fu,i 为点 u 取到离散化后第 i 小的权值的概率。

u 的左儿子为 x,有转移:

fu,i=fx,i×(puj=1ify,j+(1pu)j=i+1fy,j)

右儿子同理。

考虑线段树合并的过程是怎么保证复杂度的:在 merge(p,q) 时,如果 p,q 都不为空它才会继续往下递归。

所以,对于 merge(p,q)

  • 假如 p 为空:此时相当于 q 这个节点对应的区间内每一个位置都乘上一个数,打区间乘的标记即可。
  • 假如 q 为空:同理。
  • 都不为空:递归下去,然后上传新的值。

但转移方程内层的 怎么处理?

定义 [lx,rx] 为线段树上点 x 对应的区间。因为 是一个前(后)缀的形式,所以在合并过程中记录 [1,lp],[1,lq] 这两个区间的值之和即可。

待补:有懒标记的线段树合并方法

时间复杂度 O(nlogn),常数巨大。

代码

看起来我的 merge 比别人写得优美一些。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
#define For(Ti,Ta,Tb) for(int Ti=(Ta);Ti<=(Tb);++Ti)
#define Dec(Ti,Ta,Tb) for(int Ti=(Ta);Ti>=(Tb);--Ti)
#define Debug(...) fprintf(stderr,__VA_ARGS__)
typedef long long ll;
const int N=3e5+5,LogN=20,Mod=998244353;
long long Pow(long long _base,long long _pow,const long long& _mod){
	long long _res=1;
	while(_pow){
		if(_pow&1) _res=_res*_base%_mod;
		_pow>>=1,_base=_base*_base%_mod;
	}
	return _res%_mod;
}
struct SegmentTree{
	struct Node{
		int l,r,ls,rs;
		ll s,Mul=1;
		void PushMul(ll k){s=s*k%Mod,Mul=Mul*k%Mod;}
	}t[N*LogN];
	int tot=0;
	int New(int l,int r){
		t[++tot].l=l,t[tot].r=r;
		return tot;
	}
	void Pushup(int p){t[p].s=(t[t[p].ls].s+t[t[p].rs].s)%Mod;}
	void Pushdown(int p){
		if(!p) return;
		if(t[p].Mul!=1) t[t[p].ls].PushMul(t[p].Mul),t[t[p].rs].PushMul(t[p].Mul);
		t[p].Mul=1;
	}
	void Modify(int p,int pos,ll k){
		if(t[p].l==t[p].r){
			t[p].s=k;return;
		}
		Pushdown(p);
		int mid=(t[p].l+t[p].r)/2;
		if(pos<=mid) Modify(t[p].ls?t[p].ls:(t[p].ls=New(t[p].l,mid)),pos,k);
		else Modify(t[p].rs?t[p].rs:(t[p].rs=New(mid+1,t[p].r)),pos,k);
		Pushup(p);
	}
	int Merge(int p,int q,ll ps,ll qs,ll prob){
		if(!p&&!q) return 0;
		if(!p||!q){
			if(!p) swap(p,q),swap(ps,qs);
			t[p].PushMul((qs*prob+(Mod+1-qs)*(Mod+1-prob))%Mod);
			return p;
		}
		Pushdown(p),Pushdown(q);
		ll pls=t[t[p].ls].s,qls=t[t[q].ls].s;
		t[p].ls=Merge(t[p].ls,t[q].ls,ps,qs,prob);
		t[p].rs=Merge(t[p].rs,t[q].rs,(ps+pls)%Mod,(qs+qls)%Mod,prob);
		Pushup(p);
		return p;
	}
	void Get(int p,vector<int> &res){
		if(!p) return;
		if(t[p].l==t[p].r){
			res.push_back(t[p].s);return;
		}
		Pushdown(p);
		Get(t[p].ls,res),Get(t[p].rs,res);
	}
}seg;
int n,root[N],ch[N][2],discv[N];ll p[N];
vector<ll> disc;
void Dfs(int u){
	if(!ch[u][0]){
		root[u]=seg.New(1,disc.size()),seg.Modify(root[u],discv[u],1);
	}else if(!ch[u][1]){
		Dfs(ch[u][0]);
		root[u]=root[ch[u][0]];
	}else{
		Dfs(ch[u][0]),Dfs(ch[u][1]);
		root[u]=seg.Merge(root[ch[u][0]],root[ch[u][1]],0,0,p[u]);
	}
}
int main(){
	#ifndef zyz
	ios::sync_with_stdio(false),cin.tie(nullptr);
	#endif
	cin>>n;
	For(i,1,n){
		int fa;cin>>fa;
		if(fa) ch[fa][ch[fa][0]!=0]=i;
	}
	ll inv1w=Pow(10000,Mod-2,Mod);
	For(i,1,n){
		ll x;cin>>x;
		if(!ch[i][0]) p[i]=x,disc.push_back(x);
		else p[i]=1LL*x*inv1w%Mod;
	}
	sort(disc.begin(),disc.end());
	For(i,1,n) if(!ch[i][0]) discv[i]=lower_bound(disc.begin(),disc.end(),p[i])-disc.begin()+1;
	Dfs(1);
	vector<int> ans;
	ans.push_back(0);
	seg.Get(root[1],ans);
	ll Ans=0;
	for(int i=1;i<int(ans.size());++i){
		Ans=(Ans+1LL*i*disc[i-1]%Mod*ans[i]%Mod*ans[i]%Mod)%Mod;
	}
	cout<<Ans;
	return 0;
}

作者:alan-zhao-2007

出处:https://www.cnblogs.com/alan-zhao-2007/p/p5298-sol.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   Alan_Zhao_2007  阅读(35)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示
more_horiz
keyboard_arrow_up dark_mode palette
选择主题