HDU 5449 Robot Dog(树形DP+在线倍增LCA)

Description
一棵n个节点的树,树上有k个宝石,编号1~k,现在从起点s放一条电子狗,电子狗在每个节点往各邻接点走的概率相同,问电子狗按编号顺序拿完所有宝石的期望步数
Input
第一行一整数T表示用例组数,每组用例首先输入一整数n表示点数,之后n-1行每行两个整数u和v表示u和v在树上有一条边,之后输入一整数q表示查询数,最后q行每行首先输入一个整数k表示宝石数量,然后输入一整数s表示电子狗起点,最后k个整数表示编号从1~k的宝石在树上的位置(T<=10,2<=n<=50000,q<=100,0<=k<=500)
Output
对于每次查询,输出电子狗按顺序拿完所有宝石的期望步数,相邻两组用例的输出用一空行隔开
Sample Input
2
3
1 0
1 2
2
1 0 1
2 0 2 1
4
0 1
2 0
3 0
1
3 0 1 0 1
Sample Output
1.0000
5.0000

11.0000
Solution
关键在于如何求从u点走到v点的期望,考虑到树的性质,不妨取0点为根节点,如果能够求出0点到i点的期望步数down[i]以及i点到0点的期望步数up[i],令w=lca(u,v),那么从u点到v点的期望步数就是up[u]-up[w]+down[v]-down[w],所以问题转化为求up数组和down数组,这两个数组显然是可以通过树形dp去解决,关键在于如何转移,也就是说如何求出i点到父亲fa的期望步数u[i]以及父亲fa到i点的期望步数d[i],下面来解决这个问题,令son[i]为i节点的儿子节点数量,size[i]为以i节点为根节点的子树中节点数量
一.首先求u数组,u[i]表示i点到父亲节点fa的期望步数,i到fa的方法有两种,第一种是直接往上走到fa,第二种是先到i的某个儿子v,然后再从v到i到fa,故有
这里写图片描述
1.若i点是叶子节点,那么son[i]=0,u[i]=1
2.若i不是叶子节点,由于以i为节点的子树中每个点的父亲节点唯一,考虑上式右边的累加部分,每个节点在计算过程中作为父亲会贡献一个1,作为儿子会贡献一个1(除i节点本身),故有u[i]=2*size[i]-1
二.然后求d数组,d[i]表示从父亲节点fa到i点的期望步数,fa到i的方法有三种,第一种是直接往下走到i,第二种是走到fa的父亲节点再走回到fa再走到i,第三种是走到i的某个兄弟节点v然后从v走到fa再走到i,故有
这里写图片描述
考虑从0到i点的序列0->v1->v2->…->vm->i,根据上式有
d[i]=d[v1]+2size[v1]-2size[2]+…+2size[vm]-2size[i]=d[v1]+2size[v1]-2size[i]
而求0到其儿子节点v的期望步数有下式
这里写图片描述
故有d[i]=2n-2size[i]-1
根据u[i]=2size[i]-1和d[i]=2n-2size[i]-1,首先一遍dfs求出size[i],之后进行一遍树形dp即可求得up数组和down数组,用在线倍增法预处理,对于每两个点u和v,O(logn)查询其lca,之后根据up值和down值O(1)求出从u到v的期望步数,总复杂度O(nlogn+qklogn)
Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
#define maxn 55555
vector<int>g[maxn];
int T,n,q,p[maxn][16],deep[maxn],size[maxn];
ll up[maxn],down[maxn];
void init(int u,int fa)
{
    size[u]=1;
    for(int i=0;i<g[u].size();i++)
    {
        int v=g[u][i];
        if(v==fa)continue;
        deep[v]=deep[u]+1;
        p[v][0]=u;
        init(v,u);
        size[u]+=size[v];
    }
}
void lca_init(int n,int root)
{
    deep[0]=1;
    memset(p,-1,sizeof(p));
    init(0,0);
    for(int j=1;(1<<j)<=n;j++)
        for(int i=1;i<=n;i++)
            if(~p[i][j-1])
                p[i][j]=p[p[i][j-1]][j-1];
}
int lca(int a,int b)
{
    int i,j;
    if(deep[a]<deep[b])swap(a,b);
    for(i=0;(1<<i)<=deep[a];i++);
    i--;
    for(j=i;j>=0;j--)
        if(deep[a]-(1<<j)>=deep[b])
            a=p[a][j];
    if(a==b) return a;
    for(j=i;j>=0;j--)
    {
        if(p[a][j]!=-1&&p[a][j]!=p[b][j])
        {
            a=p[a][j];
            b=p[b][j];
        }
    }
    return p[a][0];
}
void dfs(int u,int fa)
{
    if(fa!=-1)
    {
        up[u]=up[fa]+2ll*size[u]-1;
        down[u]=down[fa]+2ll*(n-size[u])-1;
    }
    else up[u]=down[u]=0;
    for(int i=0;i<g[u].size();i++)
    {
        int v=g[u][i];
        if(v==fa)continue;
        dfs(v,u);
    }
}
void Solve()
{
    int k,u,v,w;
    ll ans=0;
    scanf("%d%d",&k,&u);
    while(k--)
    {
        scanf("%d",&v);
        w=lca(u,v);
        ans+=up[u]-up[w]+down[v]-down[w];
        u=v;
    }
    printf("%I64d.0000\n",ans);
}
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        for(int i=0;i<n;i++)g[i].clear();
        for(int i=1;i<n;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            g[u].push_back(v),g[v].push_back(u);
        }
        lca_init(n,0);
        dfs(0,-1);
        scanf("%d",&q);
        while(q--)Solve();
        if(T)printf("\n");
    }
    return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值