Description

  链接

Solution

  对于每个\(k\),统计任选\(k\)个点作为关键点的“最小生成树”的大小之和

  

  正向想法是枚举或者计算大小为\(x\)、叶子数目为\(y\)的子树有多少种,然后贡献答案。这种方法参数多、难统计,可以感受到无法适应\(1e5\)的数据,舍弃

  

  正难则反,自顶向下正向统计难,就考虑自底向上贡献统计。那么这里的自底向上,就应该是对于每一个点,统计其贡献到每个\(ans\)的次数,并累加。

  

  既然要输出k=1...m的答案,可以猜到贡献是一个卷积加速的形式

  

  所以先考虑每个点对某一个k的答案的贡献

  

  任选k个点之后,一个点对答案有1的贡献,当且仅当选择的点不全在以其为根时的某棵子树中

  

  这个很好统计,不全在某棵子树中这个条件很难考虑,不如直接用总数减去不合法的方案,毕竟所有元素用一个组合数就可以搞定\({n \choose k}-\sum_v {size_v\choose k}\)

  

  则

\[ans_k=\sum_{u=1}^n{n \choose k}-\sum_{v\in \text{sub}_u}{size_v \choose k}
\]

  前一部分可以直接算,但后一部分看起来不是一个数组的卷积

  

  遇到这种情况,我们可以用权值作为下标先做一个统计数组\(a[size_v]++\),因为统计时使用的数据与这个\(size_v\)具体是哪一个点的子树大小关系不大,而只和子树大小这个数值有关。因此不以每个点作为视角考虑(具体是谁不重要),而以整棵树为视角考虑,那么\(ans_k\)就会变成

\[ans_k=n{n\choose k}-\sum_{i=1}^{n-1}a_i{i \choose k}
\]

  减法卷积算出每个\(ans_k\)的负部分即可

Code

#include <cstdio>
using namespace std;
namespace IO{
const int S=10000000;
char buf[S];
int pos;
void load(){
fread(buf,1,S,stdin);
pos=0;
}
char getChar(){
return buf[pos++];
}
int getInt(){
int x=0,f=1;
char c=getChar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getChar();}
while('0'<=c&&c<='9'){x=x*10+c-'0';c=getChar();}
return x*f;
}
}
using IO::getInt;
const int N=200005;
const int MOD=924844033,G=5;
int n;
int h[N],tot;
struct Edge{
int v,next;
}e[N*2];
int size[N],sum[N];
int fact[N],iact[N];
inline void swap(int &x,int &y){
x^=y^=x^=y;
}
void addEdge(int u,int v){
e[++tot]=(Edge){v,h[u]}; h[u]=tot;
e[++tot]=(Edge){u,h[v]}; h[v]=tot;
}
void readData(){
n=getInt();
int u,v;
for(int i=1;i<n;i++){
u=getInt(); v=getInt();
addEdge(u,v);
}
}
void dfs(int u,int fa){
size[u]=1;
for(int i=h[u],v;i;i=e[i].next)
if((v=e[i].v)!=fa){
dfs(v,u);
size[u]+=size[v];
sum[size[v]]++;
}
sum[n-size[u]]++;
}
int fmi(int x,int y){
int res=1;
for(;y;x=1ll*x*x%MOD,y>>=1)
if(y&1)
res=1ll*res*x%MOD;
return res;
}
void init(){
fact[0]=1;
for(int i=1;i<=n;i++)
fact[i]=1ll*fact[i-1]*i%MOD;
iact[0]=iact[1]=1;
if(n>1){
iact[n]=fmi(fact[n],MOD-2);
for(int i=n-1;i>=2;i--)
iact[i]=1ll*iact[i+1]*(i+1)%MOD;
}
}
inline int C(int n,int m){
return m>n?0:1ll*fact[n]*iact[m]%MOD*iact[n-m]%MOD;
}
namespace NTT{/*{{{*/
const int S=N*4,B=19;
int n,invn,bit;
int rev[S],W[S][2];
void build(){
int iG=fmi(G,MOD-2);
for(int i=0;i<=B;i++){
W[1<<i][0]=fmi(G,(MOD-1)/(1<<i));
W[1<<i][1]=fmi(iG,(MOD-1)/(1<<i));
}
}
void init(int _n){
for(n=1,bit=0;n<_n;n<<=1,bit++);
invn=fmi(n,MOD-2);
for(int i=0;i<n;i++) rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
}
void ntt(int *a,int f){
for(int i=0;i<n;i++) if(i<rev[i]) swap(a[i],a[rev[i]]);
int w_n,w,u,v;
for(int i=2;i<=n;i<<=1){
w_n=W[i][f];
for(int j=0;j<n;j+=i){
w=1;
for(int k=0;k<(i>>1);k++){
u=a[j+k];
v=1ll*w*a[j+(i>>1)+k]%MOD;
a[j+k]=(u+v)%MOD;
a[j+(i>>1)+k]=(u-v)%MOD;
w=1ll*w*w_n%MOD;
}
}
}
if(f)
for(int i=0;i<n;i++) a[i]=1ll*a[i]*invn%MOD;
}
}/*}}}*/
void solve(){
static int a[NTT::S],b[NTT::S];
for(int i=0;i<n;i++){
a[i]=1ll*sum[i]*fact[i]%MOD;
b[i]=iact[n-1-i];
}
NTT::init(n+n-1);
NTT::ntt(a,0);
NTT::ntt(b,0);
for(int i=0;i<NTT::n;i++) a[i]=1ll*a[i]*b[i]%MOD;
NTT::ntt(a,1);
// now a[n],a[n+1],... represent k=1,2,3,...
int ans;
for(int k=1;k<=n;k++){
ans=(1ll*n*C(n,k)%MOD-1ll*iact[k]*a[n-1+k]%MOD)%MOD;
printf("%d\n",ans<0?ans+MOD:ans);
}
}
int main(){
IO::load();
NTT::build();
readData();
dfs(1,0);
init();
solve();
return 0;
}

【AGC005F】简单的问题 Many Easy Problems的更多相关文章

  1. AtcoderGrandContest 005 F. Many Easy Problems

    $ >AtcoderGrandContest \space 005 F.  Many Easy Problems<$ 题目大意 : 有一棵大小为 \(n\) 的树,对于每一个 \(k \i ...

  2. 解题:AT2064 Many Easy Problems&EXNR #1 T3 两开花

    题面 两道题比较像,放在一起写了,后者可以看成前者的加强版 (sto ztb orz) 先看AT那道题 考虑计算每个点的贡献,用容斥计算:每个点没有贡献当且仅当选的所有点都在以他为根时的一个子节点的子 ...

  3. Codeforces 913D - Too Easy Problems

    913D - Too Easy Problems 思路:二分check k 代码: #include<bits/stdc++.h> using namespace std; #define ...

  4. 【AtCoder】AGC005 F - Many Easy Problems 排列组合+NTT

    [题目]F - Many Easy Problems [题意]给定n个点的树,定义S为大小为k的点集,则f(S)为最小的包含点集S的连通块大小,求k=1~n时的所有点集f(S)的和取模92484403 ...

  5. 【CodeForces】913 D. Too Easy Problems

    [题目]D. Too Easy Problems [题意]给定n个问题和总时限T,每个问题给定时间ti和限制ai,当解决的问题数k<=ai时问题有效,求在时限T内选择一些问题解决的最大有效问题数 ...

  6. 【AGC005F】Many Easy Problems FFT 容斥原理

    题目大意 给你一棵树,有\(n\)个点.还给你了一个整数\(k\). 设\(S\)为树上某些点的集合,定义\(f(S)\)为最小的包含\(S\)的联通子图的大小. \(n\)个点选\(k\)个点一共有 ...

  7. 【AGC005F】Many Easy Problems (NTT)

    Description ​ 给你一棵\(~n~\)个点的树和一个整数\(~k~\).设为\(~S~\)为树上某些点的集合,定义\(~f(S)~\)为最小的包含\(~S~\)的联通子图的大小.\(~n~ ...

  8. AGC005F Many Easy Problems(NTT)

    先只考虑求某个f(k).考虑转换为计算每条边的贡献,也即该边被所选连通块包含的方案数.再考虑转换为计算每条边不被包含的方案数.这仅当所选点都在该边的同一侧.于是可得f(k)=C(n,k)+ΣC(n,k ...

  9. 【AGC005F】Many Easy Problems

    Description 题目链接 对于每个\(k\),统计任选\(k\)个点作为关键点的"最小生成树"的大小之和 Solution 正向想法是枚举或者计算大小为\(x\).叶子数目 ...

随机推荐

  1. 机器学习sklearn19.0聚类算法——Kmeans算法

    一.关于聚类及相似度.距离的知识点 二.k-means算法思想与流程 三.sklearn中对于kmeans算法的参数 四.代码示例以及应用的知识点简介 (1)make_blobs:聚类数据生成器 sk ...

  2. 五年.net程序员Java学习之路

    大学毕业后笔者进入一家外企,做企业CRM系统开发,那时候开发效率最高的高级程序语言,毫无疑问是C#.恰逢公司也在扩张,招聘了不少.net程序员,笔者作为应届生,也乐呵呵的加入到.net程序员行列中. ...

  3. StackOverflow 问题

    StackOverflow  这个问题一般是你的程序里头可能是有死循环或递归调用所产生的:可以查看一下你的程序,也可以增大你JVM的内存~~~在Eclipse中JDK的配置中加上   -XX:MaxD ...

  4. Linux期末总结

    Linux内核学习总结 1.计算机是如何工作的? 存储程序计算机工作模型 X86汇编基础 汇编一个简单的C程序分析其汇编指令执行过程 2.操作系统是如何工作的? 三个法宝——存储程序计算机.函数调用堆 ...

  5. Linux内核分析作业第六周

    创建新进程的过程 一.进程的描述 为了管理进程,内核必须对每个进程进行清晰的描述,进程描述符提供了内核所需了解的进程信息. 1.进程控制块PCB——task_struct 操作系统的三大管理功能 进程 ...

  6. Linux内核读书笔记第五周链接

    1.临界区(critical regions)就是访问和操作共享数据的代码段.多个执行线程并发访问同一个资源通常是不安全的,为了避免在临界区中并发访问,编程者必须保证这些代码 原子地执行.也就是说,代 ...

  7. git体会

    刘仙臣个人github链接:http://www.github.com/liuxianchen 这次作业学会了关于git的一些基本操作,学习了到了许多东西,为以后的学习奠定了基础,激发了学习的兴趣.具 ...

  8. 剑指offer:树的子结构

    题目描述: 输入两棵二叉树A,B,判断B是不是A的子结构.(ps:我们约定空树不是任意一个树的子结构) 解题思路: 同样考虑用递归来做. 利用两个递归函数,一个用于判断两棵树树否相等,另一个递归取A的 ...

  9. PAT L2-015 互评成绩

    https://pintia.cn/problem-sets/994805046380707840/problems/994805062432309248 学生互评作业的简单规则是这样定的:每个人的作 ...

  10. how-is-docker-different-from-a-normal-virtual-machine[Docker与VirtualMachine的区别]

    https://stackoverflow.com/questions/16047306/how-is-docker-different-from-a-normal-virtual-machine 被 ...