• 【洛谷5298】[PKUWC2018] Minimax(树形DP+线段树合并)


    点此看题面

    大致题意: 有一棵树,给出每个叶节点的点权(互不相同),非叶节点(x)至多有两个子节点,且其点权有(p_x)的概率是子节点点权较大值,有(1-p_x)的概率是子节点点权较小值。假设根节点(1)号节点的点权有(m)种可能性,其中权值第(i)小的可能点权是(V_i),可能性为(D_i),求(sum_{i=1}^micdot V_icdot D_i^2)

    前言

    好妙的题目,像我这种蒟蒻根本想不到线段树合并还可以这么玩。

    同时,在无数个地方漏掉(PushDown)的我感觉自己真是弱到连线段树都不会了......

    题意转化

    由于题目中保证(0<p_x<1),所以每种点权都可能被取到。

    如果我们将点权排个序,那么(i)(V_i)都是显然的,只要想个办法求出(D_i)即可。

    推式子

    首先,我们把权值离散化。

    (f_{x,i})表示节点(x)的权值为(i)的概率,则(D_i=f_{1,i})

    考虑如何转移。

    如果(x)没有子节点,设其给定权值为(v),那么(f_{x,i}=[i=v])

    如果(x)只有一个子节点,设其为(son),那么(f_{x,i}=f_{son,i})

    如果(x)有两个子节点,分别为(lc)(rc)

    题目告诉我们,权值是互不相同的。

    则对于一个权值(i),若其满足(f_{lc,i}>0),就说明这个权值在左子树中。

    下面便以在左子树中的权值(i)为例,讲讲(f_{x,i})如何转移,而在右子树中是同理的。

    我们知道,有(p_x)的概率,(x)的权值取较大值,则此时(i)应大于右儿子的权值,即概率为(p_x(sum_{k=1}^{i-1}f_{rc,k}))

    同理,有((1-p_x))的概率,(x)的权值取较小值,则此时(i)应小于右儿子的权值,即概率为((1-p_x)(sum_{k=i+1}^mf_{rc,k}))

    而这个概率实际上还要乘上(f_{lc,i}),所以:

    [f_{x,i}=f_{lc,i}cdot(p_x(sum_{k=1}^{i-1}f_{rc,k})+(1-p_x)(sum_{k={i+1}}^mf_{rc,k})) ]

    线段树合并

    仔细观察上面的式子,就可以发现,(sum_{k=1}^{i-1}f_{rc,k})(sum_{k={i+1}}^mf_{rc,k})分别是前缀和与后缀和。

    现在我们需要一个数据结构,能够维护数组之间的转移,还在前缀和与后缀和的维护方面有优势,于是不难想到动态开点线段树。

    而这个转移的过程,可以用线段树合并来实现。

    用线段树合并有什么好处呢?

    就是在线段树合并的过程中,我们可以同时维护下(f_{lc})(f_{rc})的前缀和与后缀和,这样就能很方便地进行转移了。

    当遇到某一个节点,在一棵树中为空,一棵树中非空,我们就可以借助维护下的前缀和与后缀和,打一个乘法标记,便可完成转移了。

    具体实现详见代码。

    代码

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 300000
    #define LN 20
    #define X 998244353
    #define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
    using namespace std;
    int n,ee,dc,a[N+5],dv[N+5],Rt[N+5],lnk[N+5];struct edge {int to,nxt;}e[N+5];
    I int Qpow(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}
    class FastIO
    {
    	private:
    		#define FS 100000
    		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
    		#define tn (x<<3)+(x<<1)
    		#define D isdigit(c=tc())
    		char c,*A,*B,FI[FS];
    	public:
    		I FastIO() {A=B=FI;}
    		Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
    }F;
    template<int PS> class SegmentTree//动态开点线段树
    {
    	private:
    		#define PD(x) F[x]^1&&
    		(
    			V[S[x][0]]=1LL*V[S[x][0]]*F[x]%X,F[S[x][0]]=1LL*F[S[x][0]]*F[x]%X,
    			V[S[x][1]]=1LL*V[S[x][1]]*F[x]%X,F[S[x][1]]=1LL*F[S[x][1]]*F[x]%X,F[x]=1
    		)
    		int Nt,V[PS+5],F[PS+5],S[PS+5][2];
    	public:
    		I void Ins(int& rt,CI x,CI l=1,CI r=dc)//插入
    		{
    			if(rt=++Nt,V[rt]=F[rt]=1,l==r) return;int mid=l+r>>1;
    			x<=mid?Ins(S[rt][0],x,l,mid):Ins(S[rt][1],x,mid+1,r);
    		}
    		I int Qry(CI rt,CI x,CI l=1,CI r=dc)//询问
    		{
    			if(l==r) return V[rt];int mid=l+r>>1;PD(rt);
    			return x<=mid?Qry(S[rt][0],x,l,mid):Qry(S[rt][1],x,mid+1,r);
    		}
    		I int Merge(CI x,CI y,CI p,CI l=1,CI r=dc,CI lx=0,CI rx=0,CI ly=0,CI ry=0)//线段树合并
    		{
    			if(!x&&!y) return 0;RI rt=++Nt;//皆为空直接返回
    			if(!y) return PD(x),F[rt]=(1LL*p*ly+1LL*(1-p+X)*ry)%X,//修改,注意修改前下传标记
    				V[rt]=1LL*F[rt]*V[x]%X,S[rt][0]=S[x][0],S[rt][1]=S[x][1],rt;//拷贝信息
    			if(!x) return PD(y),F[rt]=(1LL*p*lx+1LL*(1-p+X)*rx)%X,//修改,注意修改前下传标记
    				V[rt]=1LL*F[rt]*V[y]%X,S[rt][0]=S[y][0],S[rt][1]=S[y][1],rt;//拷贝信息
    			int mid=l+r>>1;PD(x),PD(y),F[rt]=1,//先下传标记
    			S[rt][0]=Merge(S[x][0],S[y][0],p,l,mid,lx,(rx+V[S[x][1]])%X,ly,(ry+V[S[y][1]])%X),//递归处理左子树
    			S[rt][1]=Merge(S[x][1],S[y][1],p,mid+1,r,(lx+V[S[x][0]])%X,rx,(ly+V[S[y][0]])%X,ry);//递归处理右子树
    			return V[rt]=(V[S[rt][0]]+V[S[rt][1]])%X,rt;//注意上传信息
    		}
    };SegmentTree<N*LN<<1> S;
    I void dfs(CI x)
    {
    	if(!lnk[x]) return S.Ins(Rt[x],lower_bound(dv+1,dv+dc+1,a[x])-dv);//处理叶节点,注意离散化
    	for(RI i=lnk[x];i;i=e[i].nxt) dfs(e[i].to),//先处理子节点
    		Rt[x]=Rt[x]?S.Merge(Rt[x],Rt[e[i].to],a[x]):Rt[e[i].to];//合并子节点
    }
    int main()
    {
    	RI i,x,ans;for(F.read(n),i=1;i<=n;++i) F.read(x),x&&add(x,i);
    	for(i=1;i<=n;++i) F.read(a[i]),lnk[i]?(a[i]=1LL*a[i]*Qpow(10000,X-2)%X):(dv[++dc]=a[i]);
    	sort(dv+1,dv+dc+1),dfs(1);//排序
    	for(ans=0,i=1;i<=dc;++i) x=S.Qry(Rt[1],i),ans=(1LL*i*dv[i]%X*x%X*x+ans)%X;//统计答案
    	return printf("%d",ans),0;
    }
    
  • 相关阅读:
    使用wchar_t输入,显示中文
    MFC使用rich edit若干问题
    一种对话框嵌入MFC 文档结构效果的实现方法(一),让你的自定义对话框区域同客户区大小一起改变
    一种在MFC程序上显示jpeg图片的方法(二)曙光乍现
    一种在MFC程序上显示jpeg图片的方法(一)宁滥勿缺
    MFC---GDI之DC类杂记,以画尺子为例
    又让厂公着半天急----一例自定义MFC程序编译时LNK2019错误
    egret 引擎分析之三————egret make --egretversion xxxx 到底做了什么?
    egret 引擎分析之二————从selector.js说起
    egret 引擎分析之一————egret 命令的时候发生了什么
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu5298.html
Copyright © 2020-2023  润新知