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

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

一开始的想法是直接求概率,然后在算期望次数,然而并不用这么麻烦。
我们设 f i 表示节点 i 走到他的父亲的期望步数, g i 表示它的父亲走到它的期望步数。
先考虑如何求 f
对于叶子节点它的 f i = 1
那其他的节点怎么求?
d i 表示节点i的度数。
对于节点i有 1 d i 的概率一步走到它的父亲,还有 1 d i 走到他的一个儿子j花费总共 f j + f i + 1 (1是有i走的j的花费)步走到i的父亲。
那么我们得到 f i = 1 d i + j S O N i f i + f j + 1 d i
移项,

f i ( 1 d i 1 d i ) = 1 d i + j S O N i f j + 1 d i

f i d i = 1 d i + j S O N i f j + 1 d i

f i = 1 + j S O N i ( f j + 1 )

f i = d i + j S O N i f j

这样我们就可以得到 f
再来考虑 g j
类似 f i ,我们可以得到 g i = 1 + 1 + g f a + g i d f a + j S O N f a , j i f j + 1 + g i d f a 。(其中fa表示i的父亲)
化简的过程也是和 f i 相同的,化简后可得
g i = f f a + g f a f i

是不是优美到自己都不敢相信,两条式子都是整数。
得到了 f g ,对询问直接求lca即可。
需要注意的是,对于根节点的儿子的 g ,用上面的式子不能直接求。
和上文的方法相同,很容易得到 g i = d r o o t + j S O N r o o t , j i f j ( i S O N r o o t )


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

猜你喜欢

转载自blog.csdn.net/kerGH/article/details/81675081