[HN省队集训day7] 分岔路口 期望+二分+点分治

题目描述

三种选择都没戏,分岔路口难救命。天要我输我不语,下个悲剧就是你。
给定一棵树,多次询问从一个点移动到另一个点最优策略的期望步数。
你有两种移动方法:沿着一条边,从一端走到另一端,花费一步;或者均匀随机选择一个点,闪现到那里,花费一步。

数据范围

n 100000 , q 200000 ,时限4s。

题目分析

由题目开篇的那首诗可知,本题为出题人报复社会所出,不可做,跳过
首先我们很容易看出,如果我们进行一次闪现操作,然后再向着终点走一步,再闪现的话,那么走的那一步就没有意义了。所以最优策略一定是:先闪现若干次,当到达距离终点小于等于 L 的点后,再直接向终点走。
假设距离到终点小于等于 L 的点有 n u m 个,它们到终点的距离和为 s u m ,称呼到终点距离小于等于 L 的点为“圈内点”。
那么圈内点到终点的期望就是它们到终点的距离,设圈外点到终点的期望是 E ,那么 E = n u m n s u m n u m + ( 1 n u m n ) E + 1 ,即 E = s u m + n n u m
这样这个期望就是可算的了,然后我们二分 L ……

桥豆麻袋,你凭什么二分?
我们二分到一个最优的 L ,那么一定有 L < E L + 1 ,我们姑且认为存在一个与 L 有关的函数 y = L E = f ( L ) + n g ( L ) s u m = f ( L ) n u m = g ( L ) ),最优值应该是在它们相交位置的附近取到。
我们知道,如果要二分一个东西,那么通常需要存在单调性。但是显然,函数 E 是可以如海浪一般起起伏伏的,没有单调性。所以我们不应该从单调性入手。
L 极小的时候,显然有 E > L ,当 L 极大的时候,显然有 L < E 。也就是说,如果能够证明交点唯一或连续,那么我们就可以二分后比较 E L 的大小来逼近这个交点,并在交点附近取到最优解。
假设交点不连续,即存在不连续的两个 L 1 L 2 ,它们都是最优解。因为 L 1 是最优解,所以距离大于 L 1 的点,进行闪现应该比进行移动更优。因为 L 2 是最优解,所以距离小于等于 L 2 的点,进行移动比闪现更优。对于 L 1 < d L 2 的点,我们不知道哪种决策更优,非常头疼。当然也有可能两种决策代价相等,不过这样夹在 L 1 L 2 之间的 L 应该也是最优解。
所以交点连续,所以可以二分。

n u m s u m 的方法不难,建立点分树,在点分树上每一个点用vector出存它的点分树子树中的点,到它距离为1的点数,为2的点数……以及点数和距离的前缀和。这样每个点最多被储存 l o g 次。然后每次从终点出发在点分树上暴力跳父亲,假设从点 y 跳到点 x ,就找点 x 子树中到 x 距离小于等于 L d i s t ( 终点 , x ) 的点数和距离和。然后再减去从 y 子树中计算来的贡献。
复杂度也是 l o g 级别的。

#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;
}

猜你喜欢

转载自blog.csdn.net/litble/article/details/80823967
今日推荐