「PKUWC2018」Minimax
Tags:线段树合并 概率DP
「PKUWC2018」Minimax
题意
有一个有n个结点的有根二叉树,对于一个点x。
如果x为叶子结点,那么权值为它本身。
如果x非叶子结点那么其权值有p的可能是子结点的最大值,有1-p的可能是子结点权值的最小值。
对于结点1的权值有m中可能,那么设权值第i小的值为V[i],概率为D[i],那么求
分析
现在仔细一看感觉好像并不是特别难的样子啊…
合并两边的点,有p的概率取较大值,有1-p的概率取较小值。
记f[i]为左边子结点取到权值为i的概率。
记g[i]为右边子结点取到权值为i的概率。
左子结点,值为i,被取的概率就是
右子结点,值为i,被取的概率就是
然后这样就可以用归并排序瞎搞然后就是50pts啦。当时考场上写出来的还蛮得意2333
疑似线段树合并?
然后因为所有权值都不相同所以说直接线段树合并就好了?合并到l=r的时候一定只有一棵线段树是有值的。然后用只需要在过程中记录两边前缀和就好了。
code
#include<bits/stdc++.h>
using namespace std;
#define M 300005
#define K 20
#define mo 998244353
#define ll long long
ll res[M];
void read(int &x){
x=0; char c=getchar();
for (;c<48;c=getchar());
for (;c>47;c=getchar())x=(x<<1)+(x<<3)+(c^48);
}
void read(ll &x){
x=0; char c=getchar();
for (;c<48;c=getchar());
for (;c>47;c=getchar())x=(x<<1)+(x<<3)+(c^48);
}
void Add(ll &x,ll y){
x=(x+y)%mo;
}
struct Marisa{
ll laz[M*K],f[M*K];
int ls[M*K],rs[M*K],tot;
void down(int p){
if (p){
if (laz[p]!=1){
if (ls[p]){
laz[ls[p]]=laz[ls[p]]*laz[p]%mo;
f[ls[p]]=f[ls[p]]*laz[p]%mo;
}
if (rs[p]){
laz[rs[p]]=laz[rs[p]]*laz[p]%mo;
f[rs[p]]=f[rs[p]]*laz[p]%mo;
}
laz[p]=1;
}
}
}
void build(int l,int r,int x,int &p){
p=++tot;
f[p]=1; laz[p]=1;
if (l==r)return ;
int mid=(l+r)>>1;
if (x<=mid)build(l,mid,x,ls[p]);
else build(mid+1,r,x,rs[p]);
}
int uni(int l,int r,ll val,ll suml,ll sumr,int lp,int rp){
down(lp); down(rp);
if (lp==0){
laz[rp]=laz[rp]*(val*suml%mo+(mo+1-val)*(mo+1-suml)%mo)%mo;
f[rp]=f[rp]*(val*suml%mo+(mo+1-val)*(mo+1-suml)%mo)%mo;
return rp;
}
if (rp==0){
laz[lp]=laz[lp]*(val*sumr%mo+(mo+1-val)*(mo+1-sumr)%mo)%mo;
f[lp]=f[lp]*(val*sumr%mo+(mo+1-val)*(mo+1-sumr)%mo)%mo;
return lp;
}
int p=++tot,mid=(l+r)>>1;
rs[p]=uni(mid+1,r,val,(suml+f[ls[lp]])%mo,(sumr+f[ls[rp]])%mo,rs[lp],rs[rp]);
ls[p]=uni(l,mid,val,suml,sumr,ls[lp],ls[rp]);
f[p]=(f[ls[p]]+f[rs[p]])%mo; laz[p]=1;
return p;
}
void pt(int l,int r,int p){
if (l==r){
res[l]=f[p];
return;
}
down(p);
int mid=(l+r)>>1;
pt(l,mid,ls[p]);
pt(mid+1,r,rs[p]);
}
}T;
int deg[M],son[M][2],rt[M],tot,a[M];
ll p[M];
void dfs(int x){
if (!deg[x]){
T.build(1,tot,p[x],rt[x]);
return ;
}
int i;
for (i=0;i<deg[x];i++){
dfs(son[x][i]);
}
if (deg[x]==1){
rt[x]=rt[son[x][0]];
}
else{
rt[x]=T.uni(1,tot,p[x],0,0,rt[son[x][0]],rt[son[x][1]]);
}
}
ll ksm(ll a,ll b){
ll res=1;
for (;b;b>>=1){
if (b&1)res=res*a%mo;
a=a*a%mo;
}
return res;
}
int ch(int l,int r,int x){
int mid;
for (;;){
mid=(l+r)>>1;
if (a[mid]==x)return mid;
if (a[mid]<x)l=mid+1;else r=mid-1;
}
}
int main(){
// freopen("1.in","r",stdin);
int n,i,f;
read(n); ll chu=ksm(10000,mo-2);
for (i=1;i<=n;i++){
read(f);
son[f][deg[f]++]=i;
}
for (i=1;i<=n;i++){
read(p[i]);
if (deg[i]){
p[i]=(chu*p[i])%mo;
}
else{
a[++tot]=p[i];
}
}
sort(a+1,a+tot+1);
tot=unique(a+1,a+tot+1)-a-1;
for (i=1;i<=n;i++)if (!deg[i]){
p[i]=ch(1,tot,p[i]);
}
dfs(1);
T.pt(1,tot,rt[1]);
ll ans=0;
for (i=1;i<=tot;i++){
res[i]=(res[i]%mo+mo)%mo;
Add(ans,res[i]*a[i]%mo*i%mo*res[i]%mo);
}
printf("%lld\n",ans);
return 0;
}
AC之后的碎碎念
然而感觉好像挺容易l和r打反死活调不出来啊…超绝望的感觉明明写的都是对的。
然而大概以后写这样子的线段树也树需要注意这样的东西…
什么lsum,rsum,ls,rs,lp,rp的。