minimax维护前后缀概率和

题目链接

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int long long
typedef unsigned long long ull;
typedef pair<ll,ll> pii;
const int inf=0x3f3f3f3f;
const int N=4e5+10;
const int mod=998244353;
const ll INF=2e9+10;
#define mid ((l+r)>>1)

int n,m,ans,tot;
int fa[N],ch[N][2],cnt[N],p[N],v[N],d[N];
int root[N],ls[N*22],rs[N*22];
int f[N*22],tag[N*22];
//v是叶子权值,d是根取值的概率 
//f节点所以可能权值的概率和 ,tag乘法懒标记 
int qpow(int a,int b){
	int res=1;
	while(b){
		if(b&1) res=res*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return res;
}

void update(int x,int v){
	f[x]=f[x]*v%mod;
	tag[x]=tag[x]*v%mod;
}

void pushdown(int x){
	if(tag[x]>1)
		update(ls[x],tag[x]),update(rs[x],tag[x]),tag[x]=1;
}

void change(int &x,int l,int r,int p){
	if(!x){
		x=++tot;tag[x]=1;
	}
	f[x]++;
	if(l==r) return;
	if(p<=mid) change(ls[x],l,mid,p);
	else change(rs[x],mid+1,r,p);
}

int merge(int x,int y,int px,int py,int sx,int sy,int p){
	if(!x&&!y) return 0;
	//因为数据保证每个叶子权值都是不一样的,也就是说子树的每个数都只能在左子树或者右子树 
	if(!y){//出现在x的时候y就不可能有这个数,让fx乘上(p*y的前缀+(1-p)*y的后缀) 
		update(x,(p*py%mod+(1-p+mod)*sy%mod)%mod);
		return x;
	}
	if(!x){
		update(y,(p*px%mod+(1-p+mod)*sx%mod)%mod);
		return y;
	}
	//比如合并2和3的时候,2子树表示的【1,2】区间在3子树没有 
	/*	  	  1  [1,4]  所以不会递归合并到4,5,在2的时候就结束了 
		   /     \		这时候是对区间【1,2】进行区间乘操作 
	[1,2] 2	      3  [3,4]  所以需要tag数组和下传tag数组 
		 / \  	 / \
		4   5   6   7
	*/
	pushdown(x),pushdown(y);
	int lx=f[ls[x]],ly=f[ls[y]],rx=f[rs[x]],ry=f[rs[y]];
	//因为fx是记录x节点代表区间的总概率和 
	//lx记录x子树当前区间的左半边的概率总和
	//rx记录x子树当前区间的右半边的概率总和
	//合并左区间的时候,右半区间概率和就是左半区间所要的后缀和
	//合并右区间的时候,左半区间概率和就是右半区间所需的前缀和
	//所以进去左区间的时候前缀和不能修改,只需要加上后缀和
	//因为对于我们要找的x节点来说,我们会往x节点去跑,
	//前缀会不断加上左边比x小的概率和,后缀也会不断加上右边比x大的概率和 
	ls[x]=merge(ls[x],ls[y],px,py,(sx+rx)%mod,(sy+ry)%mod,p);
	rs[x]=merge(rs[x],rs[y],(px+lx)%mod,(py+ly)%mod,sx,sy,p);
	f[x]=(f[ls[x]]+f[rs[x]])%mod;
	return x;
}

void dfs(int x){
	if(!ch[x][0]){//没有儿子就是叶子节点,创建线段树 
		change(root[x],1,m,p[x]);
		return;
	}
	if(!ch[x][1]){//只有一个儿子,创建完儿子直接继承 
		dfs(ch[x][0]);
		root[x]=root[ch[x][0]];
		return;
	}
	dfs(ch[x][0]),dfs(ch[x][1]);
	root[x]=merge(root[ch[x][0]],root[ch[x][1]],0,0,0,0,p[x]);
}

void dfs2(int x,int l,int r){
	if(l==r){
		d[l]=f[x];return;
	}
	pushdown(x);
	dfs2(ls[x],l,mid);
	dfs2(rs[x],mid+1,r);
}

void solve(){
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>fa[i];
	for(int i=1;i<=n;i++)
		cin>>p[i];
	for(int i=1;i<=n;i++)
		ch[fa[i]][cnt[fa[i]]++]=i;
	for(int i=1;i<=n;i++){
		if(!cnt[i]) v[++m]=p[i];
		else p[i]=1ll*p[i]*qpow(10000,mod-2)%mod;
	}
	sort(v+1,v+1+m);
	for(int i=1;i<=n;i++)
		if(!cnt[i])
			p[i]=lower_bound(v+1,v+1+m,p[i])-v;
	dfs(1);
	//可能有的点还没pushdown,还得遍历一次 
	dfs2(root[1],1,m);
	for(int i=1;i<=m;i++)
		ans=(ans+1ll*i*v[i]%mod*d[i]%mod*d[i]%mod)%mod;
	cout<<ans<<endl;
}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int t=1;
	//cin>>t;
	while(t--){
		solve();
	}
	return 0;
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值