jzoj 5814. 【NOIP提高A组模拟2018.8.14】 树(期望)

本文介绍了一种算法,用于解决在一个给定的树结构中,从任意两点出发到达最近公共祖先节点所期望经过的边数的问题。通过定义节点到其父节点的期望步数并递归求解,最终实现快速查询。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

梦游中的你来到了一棵 N 个节点的树上. 你一共做了 Q 个梦, 每个梦需要你从点 u 走到 点 v 之后才能苏醒, 由于你正在梦游, 所以每到一个节点后,你会在它连出去的边中等概率地 选择一条走过去, 为了确保第二天能够准时到校, 你要求出每个梦期望经过多少条边才能苏 醒. 为了避免精度误差, 你要输出答案模10^9 + 7的结果.


Input

第一行两个整数分别代表 N 和 Q. 接下来 N-1 行, 每行两个整数 u, v 代表树中的一条边. 接下来 Q 行, 每行两个整数代表询问的 u,v.


Output

一共 Q 行, 每行一个整数代表答案


Sample Input

4 2
1 2
2 3
3 4
1 4
3 4


Sample Output

9
5


Data Constraint

对于 20%的数据, N <= 10.
对于 40%的数据, N <= 1000.
另有 20%的数据, 保证给定的树是一条链.
对于 100%的数据, N <= 100000, Q <= 100000.


Solution

一开始的想法是直接求概率,然后在算期望次数,然而并不用这么麻烦。
我们设fifi表示节点ii走到他的父亲的期望步数,gi表示它的父亲走到它的期望步数。
先考虑如何求ff
对于叶子节点它的fi=1
那其他的节点怎么求?
didi表示节点i的度数。
对于节点i有1di1di的概率一步走到它的父亲,还有1di1di走到他的一个儿子j花费总共fj+fi+1fj+fi+1(1是有i走的j的花费)步走到i的父亲。
那么我们得到fi=1di+jSONifi+fj+1difi=1di+∑j∈SONifi+fj+1di
移项,

fi(1di1di)=1di+jSONifj+1difi(1−di−1di)=1di+∑j∈SONifj+1di

fidi=1di+jSONifj+1difidi=1di+∑j∈SONifj+1di

fi=1+jSONi(fj+1)fi=1+∑j∈SONi(fj+1)

fi=di+jSONifjfi=di+∑j∈SONifj

这样我们就可以得到ff
再来考虑gj
类似fifi,我们可以得到gi=1+1+gfa+gidfa+jSONfa,jifj+1+gidfagi=1+1+gfa+gidfa+∑j∈SONfa,j≠ifj+1+gidfa。(其中fa表示i的父亲)
化简的过程也是和fifi相同的,化简后可得

gi=ffa+gfafigi=ffa+gfa−fi​

是不是优美到自己都不敢相信,两条式子都是整数。
得到了fgf和g,对询问直接求lca即可。
需要注意的是,对于根节点的儿子的gg,用上面的式子不能直接求。
和上文的方法相同,很容易得到gi=droot+jSONroot,jifj(iSONroot)


code

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=1e5+10,mo=1e9+7;
int last[N],g[2][N],f[N][21][3],d[N],deep[N],n,m,D[N];
struct node{
    int a,b;
}a[N*2];
void dfs(int x,int y) {
    if (d[x]==1 && x!=1) {g[0][x]=1;return;}
    int s=0,i;
    for (i=last[x];i!=0;i=a[i].b){
        if (a[i].a!=y) {
            dfs(a[i].a,x);
            s=(s+g[0][a[i].a])%mo;
        }
    }
    g[0][x]=(d[x]+s)%mo;
}
void dfs1(int x,int y) {
    if (x!=1 && y!=1) g[1][x]=(g[0][y]+g[1][y]-g[0][x]+mo)%mo;
    for (int i=last[x];i!=0;i=a[i].b)
        if (a[i].a!=y) dfs1(a[i].a,x);
}
void dfs2(int x,int y) {
    if (x!=1) {
        f[x][0][2]=d[d[0]-1];
        f[x][0][0]=g[0][x];
        f[x][0][1]=g[1][x];
    }
    int i,k;
    for (i=1;(1<<i)<d[0];i++) {
        k=(1<<i);
        f[x][i][2]=d[d[0]-k];
        f[x][i][0]=(f[x][i-1][0]+f[f[x][i-1][2]][i-1][0])%mo;
        f[x][i][1]=(f[x][i-1][1]+f[f[x][i-1][2]][i-1][1])%mo;
    }
    for (int i=last[x];i!=0;i=a[i].b)
        if (a[i].a!=y) {
            deep[a[i].a]=deep[x]+1;
            d[++d[0]]=a[i].a; 
            dfs2(a[i].a,x);
            d[0]--;
        }
}
int lca(int x,int y) {
    int i,z1=0,z2=1;
    if (deep[x]<deep[y]) {
        swap(x,y);
        swap(z1,z2);
    }
    int z=0;
    while (deep[x]!=deep[y]) {
        for (i=0;deep[f[x][i][2]]>=deep[y];i++);
        i--;
        z=(z+f[x][i][z1])%mo;
        x=f[x][i][2];
    }
    while (x!=y) {
        for (i=0;f[x][i][2]!=f[y][i][2];i++);
        if (i==0) {
            z=(z+f[x][i][z1]+f[y][i][z2])%mo;
            break;
        }
        i--;
        z=(z+f[x][i][z1]+f[y][i][z2])%mo;
        x=f[x][i][2];
        y=f[y][i][2];
    }
    return z;
}
void add(int x,int y) {
    a[++a[0].a].a=y;
    a[a[0].a].b=last[x];
    last[x]=a[0].a;
}
int main() {
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    int i,j,k;
    scanf("%d%d",&n,&m);
    for (i=1;i<=n-1;i++) {
        int x,y;scanf("%d%d",&x,&y);
        add(x,y);add(y,x);
        d[x]++;d[y]++;
    }
    dfs(1,0);
    g[1][1]=0;
    int s=0;
    for (i=last[1];i!=0;i=a[i].b) s=(s+g[0][a[i].a])%mo;
    for (i=last[1];i!=0;i=a[i].b) g[1][a[i].a]=(d[1]+s-g[0][a[i].a]+mo)%mo;
    dfs1(1,0);
    deep[1]=1;d[0]=d[1]=1;
    dfs2(1,0);
    for (i=1;i<=m;i++) {
        int x,y;scanf("%d%d",&x,&y);
        printf("%d\n",lca(x,y));
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值