蓝桥杯2024国B数星星

小明正在一棵树上数星星,这棵树有 n 个结点 1,2,⋯,n。他定义树上的一个子图 G 是一颗星星,当且仅当 G 同时满足:

  1. G 是一棵树。
  2. G 中存在某个结点,其度数为 ∣VG​∣−1。其中 ∣VG​∣ 表示这个子图含有的结点数。

两颗星星不相同当且仅当它们包含的结点集合 VG​ 不完全相同。小明想知道这棵树上有多少颗不同的星星包含的结点的数量在区间 [L,R] 中,答案对 1000000007 取模。

输入格式

输入共 n+1 行。

第一行为一个正整数 n。

后面 n−1 行,每行两个正整数表示树上的一条边。

第 n+1 行,两个正整数 L,R。

输出格式

输出共 1 行,一个整数表示答案。

输入输出样例

输入 #1复制

6
1 2
1 3
2 4
2 5
3 6
3 4

输出 #1复制

6

说明/提示

【样例说明】

包含 3 个结点的星星有 5 个,它们的结点集合分别为 {1,2,3},{1,2,4},{1,2,5},{2,4,5},{1,3,6}。

包含 4 个结点的星星有 1 个,它的结点集合为 {1,2,4,5}。

思路:

首先要是一个子图 G 是一棵树,且存在一个节点的度数为 ∣VG​∣−1,也就是子图节点数量减一,显然的,这棵树的层数必须为 2,从另一个方面说,也就是一棵由一个节点,我们可以把它看成根,它连接了剩余 ∣VG​∣−1 个节点,且剩余节点之间没有连边,那么我们明白了要想产生一颗星星他所需要的条件:

  • 节点数量在范围之内。

  • 我们设子图节点数量为 x,那么它的连边数量为 x−1,这也就是树的基本特性。

  • 子图看作一棵树只有两层。

  • 存在一个节点使得它连向了剩余的所有节点,且剩余节点之间没有连边。

知道了这些就好做了,我们去遍历一棵树,每遍历到一个节点,我们就去算以他为根形成满足条件的子图数量即可,那么怎样计算满足条件的子图数量呢?现在我们设当前子图节点数量为 x,首先我们从他给定的范围开始,方案也就是从 x−1 个节点中选 l 个,从 x−1 个节点中选 l+1 个,从 x−1 个节点中选 l+2 个,一直到从 x−1 个节点中选 r 个,将选择的不同数量的相应方案数相加即可,但为什么 x 要减去 1 呢?因为要去掉根,因为根是目前连接剩余 x−1 个节点的,不能算入进去。这一步,我们可以用组合数计算出,提前与处理好阶乘和逆元,可以更快更方便的计算出以节点 i 为根产生的贡献。

代码:

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 1e9 + 7;
int n, in[100005], inv[100005], fec[100005], f[100005], l, r, res;
vector < int > g[100005];
int C (int x, int y)
{
    return inv[x] * f[y] % mod * f[x - y] % mod;
}
void dfs (int x, int fa)
{
    for (auto y : g[x])
    {
        if (y ^ fa)
            dfs(y, x);
    }
    if (static_cast<int>(g[x].size()) + 1 >= l)
    {
        
        int p = min(r - 1ll, static_cast<int>(g[x].size()));
        for (int i = l - 1;i <= p; ++ i)
        {
            res = (res + C(g[x].size(), i) % mod + mod) % mod;
        }
    }
    
}
signed main()
{
    cin >> n;
    inv[0] = fec[0] = inv[1] = fec[1] = f[0] = f[1] = 1;
    for (register int i = 2;i <= n; ++ i)
    {
        inv[i] = inv[i - 1] * i % mod;
        fec[i] = fec[mod % i] * (mod - mod / i) % mod;
        f[i] = f[i - 1] * fec[i] % mod;
    }
    for (register int i = 1;i < n; ++ i)
    {
        int x, y;
        cin >> x >> y;
        g[x].push_back(y);
        g[y].push_back(x);
    }
    cin >> l >> r;
    dfs(1, 0);
    cout << res << "\n";
    return 0;
}

AC截图:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值