【AGC005 F】Many Easy Problems

神他吗一天考一道码农题两道 FFT(其实还是我推式子一窍不通)

题意

  给你一棵 \(n\) 个点的树,再给你一个常数 \(k\)
  设 \(S\) 为树上某些点的集合,定义 \(f(S)\) 为最小的包含 \(S\) 的连通子图的大小。
  \(n\) 个点选 \(k\) 个点一共有 \(\tbinom{n}{k}\) 种方案,请求出所有方案的 \(f(S)\) 之和。
  出题人觉得这样就太简单了,他决定让你求出 \(k=1\cdots n\) 的答案。
  对于 \(27\%\) 的数据,\(n\le 2700\)
  对于 \(100\%\) 的数据,$ n\le 2\times 10^5$

题解

  这种题的关键点在于 \(n^2\) \(\text{dp}\)
  首先无法对于每个 \(k\) 快速求出答案,但我们可以考虑一个点对每个 \(k\) 的贡献。

  把 \(x\) 转为整棵树的根,则一个点 \(x\) 在这个连通子图内,当且仅当这 \(k\) 个点不都在 \(x\) 的某一个儿子子树里。

  那么贡献为 \(\tbinom{n}{k}-\sum\tbinom{a_i}{k}\),其中 \(a_i\) 为以 \(x\) 为根时,各个儿子子树的大小。
  不难发现,计算总的贡献时,\(\tbinom{n}{k}\) 会被计算 \(n\) 次,每条边两端的子树大小都会被计算一次。
  设 \(num_i\) 表示大小为 \(i\) 的子树被计算的次数(不管加还是减),则 \[b_i = \begin{cases} n\space\space\space\space\space\space\space\space\space\space\space (i=n) \\ -num_i\space (i≠n) \end{cases}\]
  其中 \(cnt_i\) 为大小为 \(i\) 的子树个数。
  由于一棵子树被且仅被被计算一次,所以 \[\begin{align} ans_k&= \sum\limits_{i\ge k} \tbinom{i}{k} b_i \nonumber \\ &= \sum\limits_{i\ge k} \frac{i!}{(i-k)!k!} b_i \nonumber \\ &= \frac{1}{k!} \sum\limits_{i\ge k} b_i i!\times \frac{1}{(i-k)!} \nonumber \end{align}\]
  此时直接计算是 \(O(n^2)\) 的,可以得到 \(27\) 分。

  我们发现 \(ans\) 像一个可以卷积的生成函数。
  又发现 \(\sum\limits_{i\ge k} b_i i!\times \frac{1}{(i-k)!} = [b_k k! \frac{1}{0!}] + [b_{k+1} (k+1)! \frac{1}{1!}] \cdots + [b_n n! \frac{1}{(n-k)!}]\)
  故我们需要构造一个序列 \(c\),使得 \(c_{0\cdots (n-k)}\) 包含了 \(b_k k!,\space b_{k+1} (k+1)!,\space\cdots ,\space b_n n!\);再构造一个序列 \(d\),使得 \(d_{0\cdots (n-k)}\) 包含了 \(\frac{1}{0!},\space \frac{1}{1!},\space \cdots,\space \frac{1}{(n-k)!}\)。我们还要使一个序列的 \(i\) 由大变小,另一个序列的 \(i\) 由小变大,这样才卷积时才会把原序列中相同位置乘起来。
  于是不难构造出 \[\begin{align} c_{n-i} &= b_i i! \nonumber \\ d_i &= \frac{1}{i!} \nonumber \\ a_i &= \sum\limits_{j+k=i} c_j d_k \nonumber \\ ans_i &= \frac{a_{n-i}}{i} \nonumber \end{align}\]
  \(\text{NTT}\) 计算出序列 \(a\) 即可。
  复杂度 \(O(n\log n)\)

#include<bits/stdc++.h>
#define ll long long
#define N 800010
#define mod 924844033
#define G 5
#define invG 554906420
using namespace std;
inline int read(){
    int x=0; bool f=1; char c=getchar();
    for(;!isdigit(c); c=getchar()) if(c=='-') f=0;
    for(; isdigit(c); c=getchar()) x=(x<<3)+(x<<1)+(c^'0');
    if(f) return x;
    return 0-x;
}
int n;
struct edge{int v,nxt;}e[N<<1];
int hd[N],cnt;
inline void add(int u, int v){e[++cnt]=(edge){v,hd[u]}, hd[u]=cnt;}
int Pow(int x, int y){
    int ret=1;
    while(y){
        if(y&1) ret=(ll)ret*x%mod;
        x=(ll)x*x%mod;
        y>>=1;
    }
    return ret;
}
int siz[N],num[N];
void dfs(int u, int fa){
    siz[u]=1;
    for(int i=hd[u]; i; i=e[i].nxt) if(e[i].v!=fa){
        dfs(e[i].v,u);
        siz[u]+=siz[e[i].v];
        --num[siz[e[i].v]], --num[n-siz[e[i].v]];
    }
}
int jc[N],jcn[N];
int a[N],b[N],c[N];
struct Poly{
    int n,bit,r[N];
    void init(int x){
        for(n=1, bit=0; n<=x; n<<=1, ++bit);
        for(int i=1; i<n; ++i) r[i] = (r[i>>1]>>1) | ((i&1)<<(bit-1));
    }
    void dft(int *a, int f){
        for(int i=0; i<n; ++i) if(i<r[i]) swap(a[i],a[r[i]]);
        for(int i=1; i<n; i<<=1){
            int wn = Pow(f==1 ? G : invG, (mod-1)/(i<<1));
            for(int j=0; j<n; j+=(i<<1)){
                int w=1, x, y;
                for(int k=0; k<i; ++k, w=(ll)w*wn%mod)
                    x=a[j+k], y=(ll)w*a[j+i+k]%mod,
                    a[j+k]=(x+y)%mod, a[j+i+k]=(x-y+mod)%mod;
            }
        }
        if(f==-1){
            int tmp=Pow(n,mod-2);
            for(int i=0; i<n; ++i) a[i]=(ll)a[i]*tmp%mod;
        }
    }
}NTT;
int main(){
    n=read();
    int u,v;
    for(int i=1; i<n; ++i) u=read(), v=read(), add(u,v), add(v,u);
    jc[0]=1;
    for(int i=1; i<=n; ++i) jc[i]=(ll)jc[i-1]*i%mod;
    jcn[n]=Pow(jc[n],mod-2);
    for(int i=n-1; i>=0; --i) jcn[i]=(ll)jcn[i+1]*(i+1)%mod;
    dfs(1,0);
    num[n]=n;
    for(int i=0; i<=n; ++i)
        b[i]=((ll)num[n-i]*jc[n-i]%mod+mod)%mod,
        c[i]=jcn[i];
    NTT.init(n*2+1), NTT.dft(b,1), NTT.dft(c,1);
    for(int i=0; i<NTT.n; ++i) a[i]=(ll)b[i]*c[i]%mod;
    NTT.dft(a,-1);
    for(int i=1; i<=n; ++i) printf("%lld\n",(ll)a[n-i]*jcn[i]%mod);
    return 0;
}

转载于:https://www.cnblogs.com/scx2015noip-as-php/p/agc010f.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值