牛客网暑期ACM多校训练营(第三场)B 数论

题目链接


题意:
现在给定一颗树的压缩方式:(按如下步骤)

  1. 先选择一个大小为 k k k的节点子集
  2. 对于不存在于子集中的所有节点,动态删去所有度为1的节点(即如果一个节点原本度为2,一个度为1的儿子被删去了,则该节点随后也应该被删去)
  3. 对于不存在于子动态集中的所有节点,动态删去所有度为2的节点,随后将其两边所练节点连接起来。
  4. 随后剩下的一棵树即为压缩以后的树

问当 k k k分别等于 1 1 1 n n n时,压缩以后的树节点个数的期望分别为多少?


思路:

k k k给定时,期望可以转化为考虑每一个节点对当前答案的贡献。即一共存在的 C n k C_{n}^k Cnk种压缩方案中,当前节点在多少种压缩方案中始终保留。

故我们可以根据当前点在原树中的度进行分类讨论:
如果当前节点的度不大于2,则只有该节点被选择成为 k k k个保留节点之一时,才不会被删除,故贡献为: C n − 1 k − 1 C_{n-1}^{k-1} Cn1k1

而对于度大于2的节点,我们可以考虑反面,假设以当前节点为根节点,如果所有保留节点都只存在于其一棵子树或者两棵子树中,则压缩后一定会被删除掉。设有 m m m棵子树,每棵子树的节点个数为: s [ 1 ] , s [ 2 ] , s [ 3 ] . . . s [ m ] s[1], s[2], s[3]...s[m] s[1],s[2],s[3]...s[m]
则贡献为总方案数减去会被删掉的方案数:

C n k − ∑ i &lt; j C s [ i ] + s [ j ] 2 C_{n}^{k} - \sum_{i&lt;j} C_{s[i]+s[j]}^2 Cnki<jCs[i]+s[j]2

但此时需要注意一个去重的问题,即对于 C s [ i ] + s [ j ] 2 C_{s[i]+s[j]}^2 Cs[i]+s[j]2,是包含 C s [ i ] 2 C_{s[i]}^2 Cs[i]2的答案,而这部分答案只应该计算一次,可当 s [ i ] s[i] s[i]与不同的 j j j计算时,该部分答案一直被重复计算,一共计算了 m − 1 m-1 m1次,而我们只需要计算一次的答案,故最后应该把重复的 ( m − 2 ) C s [ i ] 2 (m-2)C_{s[i]}^2 (m2)Cs[i]2减去
故度大于 2 2 2的节点总贡献为:

C n k − ∑ i &lt; j C s [ i ] + s [ j ] 2 + ∑ i C s [ i ] 2 C_{n}^{k} - \sum_{i&lt;j} C_{s[i]+s[j]}^2 + \sum_i C_{s[i]}^2 Cnki<jCs[i]+s[j]2+iCs[i]2

对于所有度数大于 2 2 2的节点,其计算式中的 C s [ i ] 2 C_{s[i]}^2 Cs[i]2或者 C s [ i ] + s [ j ] 2 C_{s[i]+s[j]}^2 Cs[i]+s[j]2有重复相同的,故可以考虑提前预处理进行合并,定义参数数组 c o e coe coe
c o e [ i ] coe[i] coe[i]:所有参数大于2的节点计算式合并以后, C i k C_i ^ k Cik前的系数大小。

故之后就可以枚举 k k k,再枚举每一个系数,利用 c o e [ i ] coe[i] coe[i]便可以在 O ( n ) O(n) O(n)的复杂度解决该问题了。

时间复杂度: O ( n 2 ) O(n^2) O(n2)


代码:

#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<vector>
using namespace std;
typedef long long ll;

const int mod = 1e9 + 7;
const int A = 1e4 + 10;
class Gra{
public:
    int v,next;
}G[A<<1];
int head[A], deg[A], coe[A], tot, n, fac[A], Inv[A];

ll add(ll x, ll y){
    x += y;
    return x>=mod?x-mod:x;
}

ll sub(ll x, ll y){
    x -= y;
    return x<0?x+mod:x;
}

ll mul(ll x, ll y){
    x *= y;
    return x>=mod?x%mod:x;
}

void Add(int u, int v){
    G[tot].v = v;
    G[tot].next = head[u];
    head[u] = tot++;
}

ll fast_mod(ll n, ll m){
    ll res = 1;
    while (m) {
        if (m & 1) res = mul(res, n);
        m >>= 1;
        n = mul(n,n);
    }
    return res;
}

ll Comb(ll n, ll m){
    return mul(fac[n], mul(Inv[m], Inv[n - m]));
}

ll get_Inv(ll a){
    return fast_mod(a, mod - 2);
}

void Init(){
    memset(head, -1, sizeof(head));
    tot = 0;
    fac[0] = Inv[0] = 1;
    for (int i = 1; i < A; i++) fac[i] = mul(fac[i-1], i);
    Inv[A - 1] = get_Inv(fac[A - 1]);
    for (int i = A - 2; i >= 1; i--) {
        Inv[i] = mul(Inv[i + 1], i + 1);
    }
}

void update(const vector<int>& vec){
    int m = vec.size();
    for (int i = 0; i < m; i++) {
        for (int j = i + 1; j < m; j++) {
            coe[vec[i] + vec[j]] = sub(coe[vec[i] + vec[j]], 1);
        }
    }
    for (int i = 0; i < m; i++) coe[vec[i]] = add(coe[vec[i]], m - 2);
}

int dfs(int u, int pre){
    vector<int> siz;
    int sum = 1;
    for (int i = head[u]; i != -1; i = G[i].next) {
        int v = G[i].v;
        if (v == pre) continue;
        int now = dfs(v, u);
        siz.push_back(now);
        sum += now;
    }
    if (sum < n) siz.push_back(n - sum);
    if (deg[u] > 2) update(siz);
    return sum;
}

void solve(){
    int cnt = 0;
    for (int i = 1; i <= n; i++) {
        if (deg[i] <= 2) cnt++;
    }
    for (int k = 1; k <= n; k++) {
        ll ans = mul(cnt, Comb(n - 1, k - 1));
        ans = add(ans, mul(n - cnt, Comb(n, k)));
        for (int i = k; i <= n; i++) {
            ans = add(ans, mul(coe[i], Comb(i, k)));
        }
        ans = mul(ans, get_Inv(Comb(n, k)));
        printf("%lld\n", ans);
    }
}

int main(){
    Init();
    scanf("%d", &n);
    for (int i = 1; i < n; i++) {
        int u, v;
        scanf("%d%d", &u, &v);
        Add(u, v); Add(v, u);
        deg[u]++;deg[v]++;
    }
    dfs(1, 1);
    solve();
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值