AtcoderGrandContest 005 F. Many Easy Problems

$ >AtcoderGrandContest \space 005 F.  Many Easy Problems<$

题目大意 :
有一棵大小为 \(n\) 的树,对于每一个 \(k \in[1,n]\) ,求出在所有在树中选 \(k\) 个点的方案对应的包含这 \(k\) 个点的最小联通块大小之和

\(1 \leq n \leq 2 \times 10^5\)

解题思路 :

容易发现,对于一组选取方案,包含它的最小联通块是唯一的,不妨考虑每一个点对这个联通块的贡献.

观察发现,一个点如果在一个最小联通块中,当且仅当有两个选取点的简单路径经过它

那么点 \(u\)\(k\) 个点的贡献就是 \(C_n^k -\sum_{v} C_{sz[v]}^k-C_{n-sz[u]}^k\)

观察发现这个式子只和 \(sz\) 有关,不妨设 \(tot[i]\) 表示 \(sz[u]=i\) 的点的数量

考虑除根以外的每一个点只会在其父亲计算的时候被减去一个 \(C_{sz[u]}^k\) ,同时每一种 \(sz[u]\) 都会在计算大小为 \(n-sz[u]\) 的子树的时候被减去一次

所以 \(C_{sz[u]}^k\) 的被计算次数是 \(tot[sz[u]] + tot[n-sz[u]]\)

那么最终答案的式子就是 \(Ans_j =n \times C_n^j -\sum_{i=1}^n (tot[i]+tot[n-i])\times C_i^j\)

\(inv[i]\) 表示 \(i!\) 关于 \(Mod\) 的逆元,将后面的组合数拆开来可以得到

\(Ans_j =n \times C_n^j -\sum_{i=1}^n (tot[i]+tot[n-i])\times i! \times inv[j] \times inv[i-j]\)

\(A[i] = (tot[i]+tot[n-i])\times i!\) ,则 \(Ans_j = n \times C_n^j \times inv[j] - \sum_{i=1}^nA[i]\times inv[i-j]\), 后者 \(NTT\) 进行计算即可


/*program by mangoyang*/
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#include<bits/stdc++.h>
#define inf (0x7f7f7f7f)
#define Max(a, b) ((a) > (b) ? (a) : (b))
#define Min(a, b) ((a) < (b) ? (a) : (b))
typedef long long ll;
using namespace std;
template <class T>
inline void read(T &x){
    int f = 0, ch = 0; x = 0;
    for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = 1;
    for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - 48;
    if(f) x = -x;
}
 
#define int ll
 
const int N = 1605005, L = 200005, P = 924844033, G = 5;
vector<int> g[N];
int inv[N], iv[N], s[N], f[N], tot[N], sz[N], js[N], n;
inline int Pow(int a, int b){
    int ans = 1;
    for(; b; b >>= 1, a = a * a % P)
        if(b & 1) ans = ans * a % P;
    return ans;
}
namespace NTT{
    int rev[N];
    inline int Getrev(int ned){
        int lg = 0, len = 1;
        for(; len <= ned; len <<= 1, lg++);
        for(int i = 0; i < len; i++) 
            rev[i] = (rev[i>>1] >> 1) | ((i & 1) << (lg - 1));
        return len; 
    }
    inline void DFT(int *A, int len, int type){
        for(int i = 0; i < len; i++) if(i < rev[i]) swap(A[i], A[rev[i]]);
        for(int k = 2; k <= len; k <<= 1){
            int w = Pow(G, (P - 1) / k); if(type == -1) w = Pow(w, P - 2);
            for(int i = 0; i < len; i += k){
                int now = 1;
                for(int j = i; j < i + (k >> 1); j++, (now *= w) %= P){
                    int x = A[j], y = (now * A[j+(k>>1)]) % P;
                    A[j] = (x + y) % P, A[j+(k>>1)] = (x - y + P) % P;
                }
            }   
        }
        if(type == -1){
            int now = Pow(len, P - 2);
            for(int i = 0; i < len; i++) (A[i] *= now) %= P;
        }
    }
    inline void Times(int *A, int *B, int lena, int lenb){
        int len = Getrev(lena + lenb + 1);
        DFT(A, len, 1), DFT(B, len, 1);
        for(int i = 0; i < len; i++) A[i] = A[i] * B[i] % P;
        DFT(A, len, -1);
    }  
}
inline void dfs(int u, int fa){
    sz[u] = 1, f[u] = fa;
    for(int i = 0; i < g[u].size(); i++)
        if(g[u][i] != fa) dfs(g[u][i], u), sz[u] += sz[g[u][i]];
}
inline int C(int x, int y){ 
    return js[x] * inv[y] % P * inv[x-y] % P; 
}
signed  main(){
    read(n), js[0] = 1, inv[0] = iv[L] = 1;
    for(int i = 1; i <= n; i++){
        js[i] = js[i-1] * i % P;
        iv[L-i] = inv[i] = Pow(js[i], P - 2);
    }
    for(int i = 1, x, y; i < n; i++){
        read(x), read(y);
        g[x].push_back(y), g[y].push_back(x);
    }
    dfs(1, 0);
    for(int i = 2; i <= n; i++) tot[sz[i]]++;
    for(int i = 1; i <= n; i++) 
        s[L+i] = (tot[i] + tot[n-i]) * js[i] % P;
    NTT::Times(s, iv, L + n + 1, L + n + 1);
    for(int i = 1; i <= n; i++){
        int A = n * C(n, i) % P;
        int B = inv[i] * s[2*L+i] % P;
        printf("%lld\n", ((A - B) % P + P) % P);
    }
    return 0;
}

转载于:https://www.cnblogs.com/mangoyang/p/9723310.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值