2020百度之星初赛第三场 Ant(概率)

Problem Description
有一棵 n 个节点的石榴树,点的下标为 1…n,下标为 m 的节点上有一颗石榴。石榴树有 2n−2 条有向边,每条有向边都连接两个不同的节点,不存在完全相同的两条有向边,且如果从节点 a 到节点 b 的有向边存在,反过来从节点 b 到节点 a 的有向边也必定存在。从任意一个节点都能(经过这些有向边中的若干条)到达任意一个其它节点。

现在有两只小蚂蚁去找石榴。

两只小蚂蚁都从 1 号点出发,第一只蚂蚁先走。它每次会从它当前所在的节点出发的没有走过的有向边中等概率地选择一条走(注意从 a 到 b 和从 b 到 a 是两条不同的有向边),并且在这条有向边上留下信息素。如果蚂蚁找到了石榴,或者无路可走了,它就会停下来。

第一只蚂蚁停下来以后第二只蚂蚁走,它每次从它当前所在的节点出发的有第一只蚂蚁留下的信息素且自己没走过的有向边中等概率地选择一条走。如果第二只蚂蚁找到了石榴,或者它无路可走了,它就会停下来。

如果第二只蚂蚁沿着从 1 到 m 的最短路找到了石榴,就说它成功了。请问对于第一只蚂蚁,使得第二只蚂蚁成功率大于等于 1/2 的概率是多少?

Input
第一行一个正整数 test (1≤test≤100) 表示数据组数。

对于每组数据,第一行两个正整数 n,m (1≤n≤100000,1≤m≤n) 分别表示点数和石榴在哪里。

接下来 n−1 行描述 2n−2 条有向边,每行两个整数 x,y 代表从 x 到 y 和从 y 到 x 各有一条有向边。

数据保证读入是一棵合法的石榴树,数据保证树是这样随机生成的:

for (int i = 1; i <= n; i++)
a[i] = i;
random_shuffle(a + 1, a + n + 1);
for (int i = 2; i <= n; i++)
连接编号为 a[i] 和 a[j] 的点,其中 j 为 {1, ..., i-1} 中一个随机的整数。每次随机相互独立。

(实际上每次随机使用了C++自带的随机函数。)

Output
对于每组数据,一行一个数表示答案。由于答案 A/B 中的 AB 可能很大,请输出 A/Bmod(109+7)。假设 A/B 为最简分数,A/Bmod(109+7)=A×B−1mod(109+7),B−1 为满足 B−1×Bmod(109+7)=1 的整数。

Sample Input
3
1 1
4 2
1 2
1 3
1 4
5 4
1 2
1 3
3 4
3 5

Sample Output
1
666666672
416666670

Source
2020 年百度之星·程序设计大赛 - 初赛三

思路:
因为第二只蚂蚁只会根据第一只蚂蚁留下信息素的路径走,所以只需要考虑第一只蚂蚁是否走到石榴即可。

走到石榴分为两种情况,一种是直接走到,这个路径是唯一的;另一种是“拐弯”再走到。

  1. 直接走到,那就直接递归的走,就是 f 1 [ x ] = f 1 [ v ] s i z e [ x ] f1[x]=\frac{f1[v]}{size[x]} f1[x]=size[x]f1[v] s i z e [ x ] size[x] size[x]代表 x x x相连节点个数。
  2. 拐弯再走到,首先考虑子节点中的拐弯,就是 f 2 [ x ] = f 2 [ v ] s i z e [ x ] f2[x]=\frac{f2[v]}{size[x]} f2[x]=size[x]f2[v],这与上面相同。
  3. 再考虑当前节点的拐弯。既然是拐弯,那就肯定不能走指向石榴的那个节点,所以可供选择的节点个数是 s i z e [ x ] − 1 size[x]-1 size[x]1。只要不走到 x x x指向父节点的路径,那就一定能走回到 x x x(这不难想象,走到某个子树,无论怎么走,最后都会返祖,走向指向父节点的路径),所以有效的节点个数是 s i z e [ x ] − 1 − ( x ! = 1 ) size[x]-1-(x!=1) size[x]1(x!=1)。那么有 f 2 [ x ] + = f 1 [ x ] ∗ s i z e [ x ] − 1 − ( x ! = 1 ) s i z e [ x ] − 1 f2[x]+=f1[x]*\frac{size[x]-1-(x!=1)}{size[x]-1} f2[x]+=f1[x]size[x]1size[x]1(x!=1)
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <cmath>

using namespace std;

typedef long long ll;
const int maxn = 1e5 + 7;
const int mod = 1e9 + 7;

vector<int>G[maxn];
ll f1[maxn],f2[maxn];

ll qpow(ll x,ll n) {
    ll res = 1;
    while(n) {
        if(n & 1) {
            res = res * x % mod;
        }
        n >>= 1;
        x = x * x % mod;
    }
    return res;
}

void dfs(int x,int fa,int m) {
    if(x == m) {
        f1[x] = 1;
        return;
    }
    for(int i = 0;i < G[x].size();i++) {
        int v = G[x][i];
        if(v == fa) continue;
        dfs(v,x,m);
        int tmp = (x != 1);
        if(f1[v]) {
            f1[x] = f1[v] * qpow((ll)G[x].size(),mod - 2) % mod;//从x出发直接走到石榴的概率
            f2[x] = f2[v] * qpow((ll)G[x].size(),mod - 2) % mod; //从x出发走到石榴且在子树中发生拐弯的概率
            f2[x] = (f2[x] + f1[x] * qpow((ll)G[x].size() - 1,mod - 2) % mod * (ll)(G[x].size() - 1 - tmp) % mod) % mod; //从x出发走到石榴且在x也发送了拐弯的概率
        }
    }
}

int main() {
    int T;scanf("%d",&T);
    while(T--) {
        int n,m;scanf("%d%d",&n,&m);
        for(int i = 1;i <= n;i++) {
            G[i].clear();
            f1[i] = f2[i] = 0;
        }
        for(int i = 1;i < n;i++) {
            int x,y;scanf("%d%d",&x,&y);
            G[x].push_back(y);
            G[y].push_back(x);
        }
        dfs(1,-1,m);
        printf("%lld\n",(f1[1] + f2[1]) % mod);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值