题目描述
三种选择都没戏,分岔路口难救命。天要我输我不语,下个悲剧就是你。
给定一棵树,多次询问从一个点移动到另一个点最优策略的期望步数。
你有两种移动方法:沿着一条边,从一端走到另一端,花费一步;或者均匀随机选择一个点,闪现到那里,花费一步。
数据范围
,时限4s。
题目分析
由题目开篇的那首诗可知,本题为出题人报复社会所出,不可做,跳过
首先我们很容易看出,如果我们进行一次闪现操作,然后再向着终点走一步,再闪现的话,那么走的那一步就没有意义了。所以最优策略一定是:先闪现若干次,当到达距离终点小于等于
的点后,再直接向终点走。
假设距离到终点小于等于
的点有
个,它们到终点的距离和为
,称呼到终点距离小于等于
的点为“圈内点”。
那么圈内点到终点的期望就是它们到终点的距离,设圈外点到终点的期望是
,那么
,即
这样这个期望就是可算的了,然后我们二分
……
桥豆麻袋,你凭什么二分?
我们二分到一个最优的
,那么一定有
,我们姑且认为存在一个与
有关的函数
和
(
,
),最优值应该是在它们相交位置的附近取到。
我们知道,如果要二分一个东西,那么通常需要存在单调性。但是显然,函数
是可以如海浪一般起起伏伏的,没有单调性。所以我们不应该从单调性入手。
当
极小的时候,显然有
,当
极大的时候,显然有
。也就是说,如果能够证明交点唯一或连续,那么我们就可以二分后比较
和
的大小来逼近这个交点,并在交点附近取到最优解。
假设交点不连续,即存在不连续的两个
和
,它们都是最优解。因为
是最优解,所以距离大于
的点,进行闪现应该比进行移动更优。因为
是最优解,所以距离小于等于
的点,进行移动比闪现更优。对于
的点,我们不知道哪种决策更优,非常头疼。当然也有可能两种决策代价相等,不过这样夹在
和
之间的
应该也是最优解。
所以交点连续,所以可以二分。
求
和
的方法不难,建立点分树,在点分树上每一个点用vector
出存它的点分树子树中的点,到它距离为1的点数,为2的点数……以及点数和距离的前缀和。这样每个点最多被储存
次。然后每次从终点出发在点分树上暴力跳父亲,假设从点
跳到点
,就找点
子树中到
距离小于等于
终点
的点数和距离和。然后再减去从
子树中计算来的贡献。
复杂度也是
级别的。
#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
int q=0;char ch=' ';
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
return q;
}
const int N=100005,inf=0x3f3f3f3f;
typedef double db;
typedef long long LL;
int n,Q,tot,mx,rt,SZ,ti;
int h[N],ne[N<<1],to[N<<1],sz[N],f[N],vis[N],dep[N],pos[N];
int mi[18][N<<1],b[18][N<<1],bin[18],Log[N<<1];
void add(int x,int y) {to[++tot]=y,ne[tot]=h[x],h[x]=tot;}
void pre_dfs(int x,int las) {//RMQ求lca
pos[x]=++ti,b[0][ti]=x,dep[x]=dep[las]+1;
for(RI i=h[x];i;i=ne[i]) if(to[i]!=las) pre_dfs(to[i],x),b[0][++ti]=x;
}
void pre_rmq() {
bin[0]=1;for(RI i=1;i<=17;++i) bin[i]=bin[i-1]<<1;
Log[0]=-1;for(RI i=1;i<=ti;++i) Log[i]=Log[i>>1]+1;
for(RI i=1;i<=ti;++i) mi[0][i]=dep[b[0][i]];
for(RI j=1;j<=Log[ti];++j)
for(RI i=1;i+bin[j]-1<=ti;++i)
if(mi[j-1][i]<mi[j-1][i+bin[j-1]]) mi[j][i]=mi[j-1][i],b[j][i]=b[j-1][i];
else mi[j][i]=mi[j-1][i+bin[j-1]],b[j][i]=b[j-1][i+bin[j-1]];
}
int lca(int x,int y) {
x=pos[x],y=pos[y];if(x>y) swap(x,y);
int t=Log[y-x+1];
if(mi[t][x]<mi[t][y-bin[t]+1]) return b[t][x];
else return b[t][y-bin[t]+1];
}
int getdis(int x,int y) {return dep[x]+dep[y]-2*dep[lca(x,y)];}
void getrt(int x,int las) {
sz[x]=1;int bl=0;
for(RI i=h[x];i;i=ne[i])
if(to[i]!=las&&!vis[to[i]])
getrt(to[i],x),sz[x]+=sz[to[i]],bl=max(bl,sz[to[i]]);
bl=max(bl,SZ-sz[x]);
if(bl<mx) mx=bl,rt=x;
}
vector<LL> D[N],SD[N],FD[N],FSD[N];
void orzabs(int x,int las) {//建点分树
f[x]=las,vis[x]=1;
for(RI i=h[x];i;i=ne[i])
if(!vis[to[i]]) SZ=sz[to[i]],mx=inf,getrt(to[i],x),orzabs(rt,x);
}
void orzqys() {//建立vector
//D:某个距离的点数前缀和,SD:距离前缀和
//FD:到该点点分树上父亲的某个距离的点数前缀和,FSD:到父亲的距离前缀和
for(RI i=1;i<=n;++i) {
int x=i;
while(x) {
int kl=getdis(i,x);
while((int)(D[x].size()-1)<kl) D[x].push_back(0);
++D[x][kl];
if(f[x]) {
kl=getdis(i,f[x]);
while((int)(FD[x].size()-1)<kl) FD[x].push_back(0);
++FD[x][kl];
}
x=f[x];
}
}
for(RI i=1;i<=n;++i) {
for(RI j=0;j<D[i].size();++j) {
SD[i].push_back((j==0?0:SD[i][j-1])+1LL*j*D[i][j]);
D[i][j]+=(j==0?0:D[i][j-1]);
}
for(RI j=0;j<FD[i].size();++j) {
FSD[i].push_back((j==0?0:FSD[i][j-1])+1LL*j*FD[i][j]);
FD[i][j]+=(j==0?0:FD[i][j-1]);
}
}
}
db gete(int lim,int y) {//获得期望值
LL sum=0,num=0;int las=0,t,x=y;
while(y) {
int kd=getdis(y,x);
if(kd<=lim) {
t=min((int)(D[y].size())-1,lim-kd);
num+=D[y][t],sum+=SD[y][t]+1LL*kd*D[y][t];
if(las) {//减去刚才统计过的子树的贡献
t=min((int)(FD[las].size())-1,lim-kd);
num-=FD[las][t],sum-=FSD[las][t]+1LL*kd*FD[las][t];
}
}
las=y,y=f[y];
}
return ((db)sum+(db)n)/(db)num;
}
void work(int x,int y) {//二分答案
int l=0,r=n,dis=getdis(x,y);db ans=1e15;
while(l<=r) {
int mid=(l+r)>>1;
db kl=gete(mid,y);
ans=min(ans,min((db)dis,kl));
if(kl>(db)mid) l=mid+1;
else r=mid-1;
}
printf("%.8lf\n",ans);
}
int main()
{
freopen("branching.in","r",stdin);
freopen("branching.out","w",stdout);
int x,y;
n=read(),Q=read();
for(RI i=1;i<n;++i) x=read(),y=read(),add(x,y),add(y,x);
pre_dfs(1,0),pre_rmq();
SZ=n,mx=inf,getrt(1,0),orzabs(rt,0),orzqys();
int ans=0;
while(Q--) x=read(),y=read(),work(x,y);
return 0;
}