2020kickstart d Beauty of tree

题意:

a,b两个人, 分别在一棵节点数为n的有根树上,随机选择一个点,每x(y)个点(往根的方向)对一个节点染色,直到到达或超过根节点。

问这棵树被染色的点的期望值。

 

解题思路:

首先有N*N个情况,每种情况下这棵树被染色的数目之和除以N*N就是要求的期望值

所以问题就算如何统计每种情况染色数目之和

暴力枚举每种情况肯定是不行的,可以换一个思路来想,去求每个点被选取后会染色多少个点。在一棵树上,假设p-x就是p下一步会染的点,那么r[p]=r[p-x]+1,只需要一遍dfs指定向下遍历就能求出每个点能染多少个点,但是有个问题,我们只能知道父节点,距离自己x个单位的祖先节点没法知道,肯定不能再套一个dfs去找,因为x可能是n,这样复杂度又是n^2了。我们可以维护一个队列que,头尾分别为head,tail, 存点的编号,向下递归的时候t当前节点入队(tail++),当tail-head>x的时候头出队(head++),回溯的时候tail-head<x的时候head--,之前的头重新入队。这样每次que[head]里存的都是距离自己x个单位的节点的编号了。

然后还有一个问题就是,会有重复染色的情况,就是a和b在某种情况下都对一个点染色了,我们统计下每个点会被a染色多少次(bra),被b染色多少次(brb),bra*brb就是a和b重复染这个点的情况, 减去就行。br的维护可以通过子树的点来求。因为每个点只会被子数上的点染色。br[p]=sum(br[q]+1,q-x=p)。

然后还有个情况是每个点被自己染色 和被a或b染色的情况重复了,这种情况有bra+brb次减去。

 

 

最后还有一个情况是a,b都选择了同一个点 这个情况也要减去,这种情况有n个。

所以最终染色数目是所有点的ra*n+rb*n-bra*brb-bra-brb加和-n

时间复杂度是O(n)。

#include <bits/stdc++.h>
#define ps push_back
using namespace std;
const int maxn=5e5+5;
vector<int>edg[maxn];
long long ra[maxn], rb[maxn], bra[maxn], brb[maxn];
int head, tail;
long long que[maxn];
void dfs(int now, long long r[maxn], long long br[maxn], long long d)
{
    que[tail]=now;
    if(tail-head>d)head++;
    if(tail-head==d)
    {
        r[now]=r[que[head]]+1;
    }
    else  //not enough p
    {
        r[now]=1;
    }
    tail++;

    for(int i=0; i<edg[now].size(); i++)
    {
        int to=edg[now][i];
        dfs(to, r, br, d);,,
    }
    
    tail--;
    if(tail-head<d && head>0)head--;
    if(tail-head==d)br[que[head]]+=br[now]+1;
//    cout<<head<<" "<<tail<<endl;
//    cout<<now<<" "<<r[now]<<" "<<br[now]<<endl;
    return;
}
void solve()
{
    long long n, a, b, x;
    cin>>n>>a>>b;
    for(int i=2; i<=n; i++){scanf("%lld", &x);edg[x].ps(i);}
    head=0, tail=0;
    dfs(1, ra, bra, a);
    head=0, tail=0;
    dfs(1, rb, brb, b);
    double ans=0, fz=1.0/(n*n);
    for(int i=1; i<=n; i++)
    {
        ans+=ra[i]*n;
        ans+=rb[i]*n;
        ans-=bra[i]*brb[i];
        ans-=(bra[i]+brb[i]);
    }
    ans-=n;
    printf("%.6f\n", ans*fz);
    for(int i=1; i<=n; i++)
    {
        ra[i]=rb[i]=bra[i]=brb[i]=0;
        edg[i].clear();
    }

}
int main()
{
//    ios::sync_with_stdio(0);
//    cin.tie(0);
    int t;
//    cout<<setprecision(7)<<0.1234567<<endl;
    cin>>t;
    for(int i=1; i<=t; i++){
        cout<<"Case #"<<i<<": ";
        solve();
    }

    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值