85 C Petya and Tree
問題
二分探索木(あるノードの左の子孫のノードは、すべて値がそのノードの値より小さく、右の子孫のノードは値がそのノードの値より大きい二分木)で、全てのノードの子ノードの数は0または2であるものが与えられる。
この木で、与えられた値に対して、通常の二分木の探索のように探索を行う。
ただし、探索の途中で一度だけ大小判定を間違ってしまう。
そのような誤りは、全て等しい確率で起こる。
このとき、辿り着く葉のノードの値の期待値を求めよ。
制約条件
n≦10^5
クエリの数≦10^5
方針
なんか上手く言語化できない。
大小比較を間違うのは必ず一回であるため、間違える箇所を決めれば辿り着くノードは一意に定まる。
どこで間違ったかが決まった後、どこに着くかを求めるのは、頂点を愚直に辿るとO(n)時間かかる。
が、「大きい」というところを「小さい」と誤判定したら、以後の判定は全て「大きい」になり、逆も同じなので、そのノードの右側のうち最も小さい葉をDPにより前計算しておくことでO(1)で計算できるようになる。
しかしこれでは一回のクエリにO(n)時間かかる。
本来の辿り着く葉がAであるようなクエリと、本来辿り着く葉がBであるようなクエリがあったとする。
ここでA,Bが同じ親をもっていたとすると、期待値の計算はほとんどの部分が同じであることがわかる。
よってこの部分が計算量を落とせそうである。
実際に、「ある頂点から、その親の頂点以降の期待値を求める関数」がメモ化でき、
これによりO(n)の前処理により、全ての葉について期待値がO(1)時間で求められるようになる。
では、本来辿り着く葉をどうやって求めるか。これも愚直にやるとO(n)時間がかかってしまう。
自分は以下のようにしてO(log n)時間で求めた。
クエリの値をxとする。
xより小さい最大の値を持つ葉をA,xより大きい最小の値を持つ葉をBとすると、本来辿り着く葉はAまたはBのどちらかである。
これがどちらであるかは、AとBの最小共通先祖のノード(これをCとする)の値とxを比較して、小さいならA,大きいならBと決定できる。
AとBはあらかじめ葉のノードを列挙してソートしておくなどして、二分探索によりO(log n)時間で求められる。
共通祖先のノードは、色々な方法があるが、蟻本の271ページの方法でO(log n)時間で求めた。
以上から、全体でO((n+q) log n)時間で全ての計算がおこなえて、制限時間に間に合う。
ソースコード
const int MAX_N = 100000; int n,k,lch[MAX_N],rch[MAX_N],p[MAX_N],val[MAX_N]; int parent[20][MAX_N]; int depth[MAX_N],mx[MAX_N],mn[MAX_N]; vector<pi> leaves; map<int,int> val_to_v; double dp[MAX_N][2],ans[MAX_N]; double calc(int node,int cld){ if(dp[node][cld]>0)return dp[node][cld]; double &ret=dp[node][cld]; ret=0; if(p[node]>=0)ret+=calc(p[node],val[node]>val[p[node]]); if(cld==0)ret+=mn[rch[node]]; else ret+=mx[lch[node]]; return ret; } void dfs(int c,int d){ depth[c]=d; if(lch[c]<0){ leaves.pb(mp(val[c],c)); mx[c]=mn[c]=val[c]; return; } dfs(lch[c],d+1); dfs(rch[c],d+1); mx[c]=max(mx[lch[c]],mx[rch[c]]); mn[c]=min(mn[lch[c]],mn[rch[c]]); } int lca(int u,int v){ if(depth[u]>depth[v])swap(u,v); rep(k,20){ if((depth[v]-depth[u])>>k&1)v=parent[k][v]; } if(u==v)return u; for(int k=19;k>=0;k--){ if(parent[k][u]!=parent[k][v]){ u=parent[k][u]; v=parent[k][v]; } } return parent[0][u]; } void run() { scanf("%d",&n); rep(i,n)lch[i]=rch[i]=-1, mx[i]=-inf, mn[i]=inf; depth[0]=0; int root; rep(i,n){ scanf("%d%d",p+i,val+i); p[i]--; parent[0][i]=p[i]; val_to_v[val[i]]=i; if(p[i]>=0){ if(val[i]<val[p[i]])lch[p[i]]=i; else rch[p[i]]=i; } } rep(i,n){ if(p[i]>=0){ if(val[i]<val[p[i]])lch[p[i]]=i; else rch[p[i]]=i; } else root=i; } dfs(root,0); for(int k=0;k+1<20;k++)rep(v,n){ if(parent[k][v]<0)parent[k+1][v]=-1; else parent[k+1][v]=parent[k][parent[k][v]]; } rep(i,n)if(lch[i]<0){ ans[i]=calc(i,val[i]>val[p[i]])/depth[i]; } const int leaf=leaves.size(); sort(all(leaves)); int k; scanf("%d",&k); rep(i,k){ int x,lo=-1,hi=leaf,mid; scanf("%d",&x); while(lo+1<hi){ mid=(lo+hi)/2; if((mid<0?-inf:mid<leaf?leaves[mid].first:inf)<x)lo=mid; else hi=mid; } //cerr<<"lo: "<<lo<<" hi: "<<hi<<endl; if(hi==leaf)x=leaves[leaf-1].second; else if(lo==-1)x=leaves[0].second; else{ lo=leaves[lo].second; hi=leaves[hi].second; mid=lca(lo,hi); if(x<val[mid])x=val_to_v[mx[lch[mid]]]; else x=val_to_v[mn[rch[mid]]]; } printf("%.9f\n",ans[x]); } }