2017.11.3 树上期望DP 解题报告

题目描述

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

输入格式

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

输出格式

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

样例

tree.in
4 2
1 2
2 3
3 4
1 4
3 4
tree.out
9
5

数据范围

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

【解题报告】

这个小树什么的是骗你的。
我们定义 up[i] 为节点 i 到根节点的期望步数,down[i]为根节点到 [i] 的期望步数
那么显然 u v的期望步数为 up[u]up[lcm(u,v)]+down[v]down[lcm(u,v)]
我们接着定义 u[i] i 节点走到它父亲的期望步数,d[i]为从 i 的父亲节点走到i的期望步数
我们可以的得到
u[i]=2size[i]1
d[i]=2n2size[i]1
一种证明方式http://blog.csdn.net/V5ZSQ/article/details/52314029
直接DP转移即可

代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define LL long long
#define N 100010
#define mod 1000000007

int n,q,cnt=-1,head[N];
struct Edge{int to,nxt;}e[N<<1];
int size[N];LL down[N],up[N];

void adde(int u,int v)
{
    e[++cnt].to=v;e[cnt].nxt=head[u];head[u]=cnt;
    e[++cnt].to=u;e[cnt].nxt=head[v];head[v]=cnt;
}
namespace LCA
{
    int dis[N],ff[18][N];
    void dfs(int p,int father) 
    {
        dis[p]=dis[father]+1,ff[0][p]=father;
        for(int i=head[p];~i;i=e[i].nxt) 
        {
            int v=e[i].to;
            if(v^father) dfs(v,p);
        }
    }
    void da() 
    {
        for(int j=1;(1<<j)<=n;++j)
        for(int i=1;i<=n;++i)
            ff[j][i]=ff[j-1][ff[j-1][i]];
    }
    int query(int x,int y) 
    {
        if(dis[x]<dis[y]) x^=y^=x^=y;
        int t=dis[x]-dis[y];
        for(int i=0;i<=17;++i)
            if(t&(1<<i)) x=ff[i][x];
        if(x==y) return x;
        for(int i=17;~i;--i)
            if (ff[i][x]^ff[i][y]) x=ff[i][x],y=ff[i][y];
        return ff[0][x];
    }
}
namespace DP
{
    void dfs1(int u,int fa)//size
    {
        size[u]=1;
        for(int i=head[u];~i;i=e[i].nxt)
        {
            int v=e[i].to;
            if(v==fa) continue;
            dfs1(v,u);
            size[u]+=size[v];
        }
    }
    void dfs2(int u,int fa)//down&up(pre_sum!!!)
    {
        for(int i=head[u];~i;i=e[i].nxt)
        {
            int v=e[i].to;
            if(v==fa) continue;
            up[v]=2*size[v]-1+up[u];
            down[v]=2*n-2*size[v]-1+down[u];
            dfs2(v,u);
        }
    }
}
using namespace LCA;
using namespace DP;
int main()
{
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    memset(head,-1,sizeof(head));
    scanf("%d%d",&n,&q);
    for(int i=1,u,v;i<n;++i)
    {
        scanf("%d%d",&u,&v);
        adde(u,v);
    }
    dfs(1,1);
    dfs1(1,1);
    up[1]=0;down[1]=0;
    dfs2(1,1);
    da();
//  for(int i=1;i<=n;++i) printf("%d %d\n",up[i],down[i]);
    for(int i=1,u,v;i<=q;++i)
    {
        scanf("%d%d",&u,&v);
//      printf("%d %d ",u,v); 
        int tmp=query(u,v);//printf(" %d ",tmp);
//      printf("%d ",tmp);
        long long ans=up[u]+down[v]-up[tmp]-down[tmp];
        printf("%lld\n",ans%mod);
    }
    return 0;
}
/*
4 2
1 2
2 3
3 4
1 4
3 4
*/
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值