洛谷 P4002 - [清华集训2017]生成树计数(多项式)
神题。
考虑将所有连通块缩成一个点,那么所有连好边的生成树在缩点之后一定是一个 \(n\) 个点的生成树。我们记 \(d_i\) 为第 \(i\) 个连通块缩完点之后的度数 \(-1\),那么共有 \(\prod\limits_{i=1}^na_i^{d_i+1}\times\dfrac{(n-2)!}{\prod\limits_{i=1}^nd_i!}\) 个这样的生成树,稍微解释一下这个柿子,因为每个连通块的每条边都有可能是由其中 \(a_i\) 个点中任意一点连出的,因此每个连通块连边的方案为 \(a_i^{d_i+1}\),而由 Prufer 序列那套理论可知 \(d_i\) 即为 \(i\) 在 Prufer 序列中的出现次数,且 \(\sum\limits_{i=1}^nd_i=n-2\),又因为一个 Prufer 序列与一棵生成树形成双射,故这样的 \(n\) 个点的生成树个数为满足 \(i\) 在序列中出现 \(d_i\) 次的长度为 \(n-2\) 的序列个数,即多重组合数 \(\dbinom{n-2}{d_1,d_2,\cdots,d_n}=\dfrac{(n-2)!}{\prod\limits_{i=1}^nd_i!}\),用乘法原理将它们乘起来即可。
因此我们要求的答案即为 \(\sum\limits_{d_1+d_2+\cdots+d_n=n-2}\prod\limits_{i=1}^na_i^{d_i+1}\times\dfrac{(n-2)!}{\prod\limits_{i=1}^nd_i!}\times(\prod\limits_{i=1}^n(d_i+1)^m)(\sum\limits_{i=1}^n(d_i+1)^m)\)。考虑将柿子稍微变个形,将 \(a_i^{d_i+1}\) 拆成 \(a_i^{d_i}\) 与 \(a_i\) 并将 \(a_i\) 提到外面来,再将 \((n-2)!\) 提到外面来,即可得到 \((n-2)!\prod\limits_{i=1}^na_i\sum\limits_{d_1+d_2+\cdots+d_n=n-2}\prod\limits_{i=1}^n\dfrac{a_i^{d_i}}{d_i!}\times(\prod\limits_{i=1}^n(d_i+1)^m)(\sum\limits_{i=1}^n(d_i+1)^m)\),将里面的两个 \(\prod\) 合并,又有 \((n-2)!\prod\limits_{i=1}^na_i\sum\limits_{d_1+d_2+\cdots+d_n=n-2}(\prod\limits_{i=1}^n\dfrac{a_i^{d_i}}{d_i!}(d_i+1)^m)(\sum\limits_{i=1}^n(d_i+1)^m)\),前面的 \((n-2)!\prod\limits_{i=1}^na_i\) 是常数,因此我们只需算出后面的答案后乘上去即可。将里面 \(\sum\) 的括号拆开,移到外面去,可得 \(\sum\limits_{d_1+d_2+\cdots+d_n=n-2}\sum\limits_{i=1}^n(d_i+1)^m(\prod\limits_{j=1}^n\dfrac{a_j^{d_j}}{d_j!}(d_j+1)^m)\),再稍微整理一下即可得到 \(\sum\limits_{d_1+d_2+\cdots+d_n=n-2}\sum\limits_{i=1}^n\dfrac{a_i^{d_i}}{d_i!}(d_i+1)^{2m}\prod\limits_{j\ne i}^n\dfrac{a_j^{d_j}}{d_j!}(d_j+1)^m\)。前面的我们 \(\sum\limits_{d_1+d_2+\cdots+d_n=n-2}\) 非常讨厌,不过长得一脸幂级数的样子,因此考虑记 \(A(x)=\sum\limits_{k\ge 0}\dfrac{x^k}{k!}(k+1)^m\),\(B(x)=\sum\limits_{k\ge 0}\dfrac{x^k}{k!}(k+1)^{2m}\),那么显然 \(\dfrac{a_i^{d_i}}{d_i!}(d_i+1)^{2m}=[x^{d_i}]B(a_ix)\),\(\dfrac{a_j^{d_j}}{d_j!}(d_j+1)^m=[x^{d_j}]A(a_jx)\),故 \(\sum\limits_{d_1+d_2+\cdots+d_n=n-2}\sum\limits_{i=1}^n\dfrac{a_i^{d_i}}{d_i!}(d_i+1)^{2m}\prod\limits_{j\ne i}^n\dfrac{a_j^{d_j}}{d_j!}(d_j+1)^m=\sum\limits_{d_1+d_2+\cdots+d_n=n-2}\sum\limits_{i=1}^n[x^{d_i}]B(a_ix)\prod\limits_{j\ne i}^n[x^{d_j}]A(a_jx)\),而显然它又等于 \([x^{n-2}]\sum\limits_{i=1}^nB(a_ix)\prod\limits_{j\ne i}^nA(a_jx)\),哦吼,\(\sum\limits_{d_1+d_2+\cdots+d_n=n-2}\),这下我们就可以直接从这两个幂级数入手了。
然鹅直接计算 \(\sum\limits_{i=1}^nB(a_ix)\prod\limits_{j\ne i}^nA(a_jx)\) 还是不太容易,考虑按照 P4389 付公主的背包 的套路取 \(\ln\) 再 \(\exp\) 回去,即 \(\sum\limits_{i=1}^nB(a_ix)\exp\sum\limits_{j\ne i}\ln A(a_jx)\),这里 \(j\ne i\) 不太美观,因此考虑直接把 \(j=i\) 的答案先统计进去,前面再除个 \(A(a_ix)\),即 \(\sum\limits_{i=1}^n\dfrac{B(a_ix)}{A(a_ix)}\exp\sum\limits_{j=1}^n\ln A(a_jx)\),这样两个 \(\sum\) 就可以独立计算了,故我们只需算出 \(\sum\limits_{i=1}^n\dfrac{B(a_ix)}{A(a_ix)}\) 和 \(\exp\sum\limits_{j=1}^n\ln A(a_jx)\),然后把它们卷起来即可,不过这里又有一个问题,就是对于所有 \(i\) 都要计算一遍 \(\dfrac{B(a_ix)}{A(a_ix)}\) 及 \(\ln A(a_jx)\),这样复杂度还是平方对数的,显然过不去。不过注意到 \([x^n]\dfrac{B(a_ix)}{A(a_ix)}=a_i^n[x^n]\dfrac{B(x)}{A(x)}\),\([x^n]\ln A(a_jx)=a_j^n[x^n]\ln A(x)\),因此我们只需预处理出 \(\dfrac{B(x)}{A(x)}\) 和 \(\ln A(x)\),那么 \(\sum\limits_{i=1}^n\dfrac{B(a_ix)}{A(a_ix)}\) 的第 \(k\) 项系数就是 \(\dfrac{B(x)}{A(x)}\) 第 \(k\) 项系数的 \(\sum\limits_{i=1}^na_i^k\) 倍,\(\sum\limits_{j=1}^n\ln A(a_jx)\) 也同理。
最后就是怎样对 \(k\in[0,n]\) 求 \(\sum\limits_{i=1}^na_i^k\),其实这个套路在 P4705 玩游戏 里就出现过了,不过这里再讲解一遍。由于 \(a^k=[x^k]\dfrac{1}{1-ax}\),因此这东西的生成函数 \(F(x)=\sum\limits_{i=1}^n\dfrac{1}{1-a_ix}\),注意到 \(\ln'(\dfrac{1}{1-a_ix})=-\dfrac{a_i}{1-a_ix}\),而 \(F(x)=\sum\limits_{i=1}^n\dfrac{1}{1-a_ix}=n-\sum\limits_{i=1}^n-\dfrac{a_i}{1-a_ix}\),故 \(F(x)=n-\sum\limits_{i=1}^n\ln'(\dfrac{1}{1-a_ix})=n+\sum\limits_{i=1}^n\ln'(1-a_ix)\),根据导数的和等于和的倒数可知,\(F(x)\) 又等于 \(n+(\sum\limits_{i=1}^n\ln(1-a_ix))'=n+(\ln\prod\limits_{i=1}^n(1-a_ix))'\),里面的 \(\prod\) 可以分治求出,复杂度 \(n\log^2n\),求出来之后再 \(\ln\) 一遍,\(\text{direv}\) 一遍即可。
时间复杂度 \(n\log^2n\)。
注意特判 \(n=1\),否则 UOJ 上过不去。
const int pr=3;
const int MAXN=3e4;
const int MAXP=1<<16;
const int MOD=998244353;
const int ipr=(MOD+1)/3;
int qpow(int x,int e=MOD-2){
int ret=1;
for(;e;e>>=1,x=1ll*x*x%MOD) if(e&1) ret=1ll*ret*x%MOD;
return ret;
}
int n,m,a[MAXN+5],fac[MAXN+5],ifac[MAXN+5];
void init_fac(int n){
fac[0]=ifac[0]=ifac[1]=1;
for(int i=2;i<=n;i++) ifac[i]=1ll*ifac[MOD%i]*(MOD-MOD/i)%MOD;
for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%MOD,ifac[i]=1ll*ifac[i]*ifac[i-1]%MOD;
}
int rev[MAXP+5],inv[MAXP+5];
void NTT(vector<int> &a,int len,int type){
int lg=31-__builtin_clz(len);
for(int i=0;i<len;i++) rev[i]=(rev[i>>1]>>1)|((i&1)<<(lg-1));
for(int i=0;i<len;i++) if(i<rev[i]) swap(a[i],a[rev[i]]);
for(int i=2;i<=len;i<<=1){
int W=qpow((type<0)?ipr:pr,(MOD-1)/i);
for(int j=0;j<len;j+=i){
int w=1;
for(int k=0;k<(i>>1);k++,w=1ll*w*W%MOD){
int X=a[j+k],Y=1ll*w*a[(i>>1)+j+k]%MOD;
a[j+k]=(X+Y)%MOD;a[(i>>1)+j+k]=(X-Y+MOD)%MOD;
}
}
}
if(type==-1) for(int i=0;i<len;i++) a[i]=1ll*a[i]*inv[len]%MOD;
}
vector<int> conv(vector<int> a,vector<int> b){
int len=1;while(len<a.size()+b.size()) len<<=1;
a.resize(len,0);b.resize(len,0);NTT(a,len,1);NTT(b,len,1);
for(int i=0;i<len;i++) a[i]=1ll*a[i]*b[i]%MOD;
NTT(a,len,-1);return a;
}
vector<int> conv(vector<int> a,vector<int> b,int lenc){
int len=1;while(len<a.size()+b.size()) len<<=1;
a.resize(len,0);b.resize(len,0);NTT(a,len,1);NTT(b,len,1);
for(int i=0;i<len;i++) a[i]=1ll*a[i]*b[i]%MOD;
NTT(a,len,-1);if(a.size()>lenc) a.resize(lenc);
return a;
}
vector<int> getinv(vector<int> a,int n){
vector<int> b(n,0);assert(a[0]!=0);b[0]=qpow(a[0]);
for(int i=2;i<=n;i<<=1){
vector<int> c(a.begin(),a.begin()+i);
vector<int> d(b.begin(),b.begin()+(i>>1));
d=conv(d,d);c=conv(c,d);
for(int j=0;j<i;j++) b[j]=(2*b[j]%MOD-c[j]+MOD)%MOD;
} return b;
}
vector<int> direv(vector<int> a,int n){
vector<int> b(n,0);
for(int i=1;i<n;i++) b[i-1]=1ll*a[i]*i%MOD;
return b;
}
vector<int> inter(vector<int> a,int n){
vector<int> b(n,0);
for(int i=1;i<n;i++) b[i]=1ll*a[i-1]*inv[i]%MOD;
return b;
}
vector<int> getln(vector<int> a,int n){
vector<int> a_=direv(a,n),b=getinv(a,n);
b=conv(a_,b);b=inter(b,n);return b;
}
vector<int> getexp(vector<int> a,int n){
vector<int> b(n,0);b[0]=1;
for(int i=2;i<=n;i<<=1){
vector<int> c(b.begin(),b.begin()+i);
vector<int> d(b.begin(),b.begin()+(i>>1));
c=getln(c,i);for(int j=0;j<i;j++) c[j]=(a[j]-c[j]+MOD)%MOD;
(c[0]+=1)%=MOD;d=conv(d,c);
for(int j=0;j<i;j++) b[j]=d[j];
} return b;
}
int LEN=1;
vector<int> solve(int l,int r){
if(l==r){return vector<int>{1,MOD-a[l]};}
int mid=l+r>>1;return conv(solve(l,mid),solve(mid+1,r),LEN);
}
int main(){
scanf("%d%d",&n,&m);init_fac(n);
if(n==1) return printf("%d\n",m==0),0;
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
while(LEN<=n) LEN<<=1;
for(int i=1;i<=LEN<<1;i++) inv[i]=qpow(i);
vector<int> A(LEN),B(LEN);
for(int i=0;i<=n;i++) A[i]=1ll*ifac[i]*qpow(i+1,m)%MOD;
for(int i=0;i<=n;i++) B[i]=1ll*ifac[i]*qpow(i+1,2*m)%MOD;
vector<int> iA=getinv(A,LEN),lnA=getln(A,LEN);
vector<int> mA=conv(B,iA,LEN);
vector<int> C=solve(1,n);C.resize(LEN,0);
C=getln(C,LEN);C=direv(C,LEN);for(int j=LEN-1;j;j--) C[j]=(MOD-C[j-1])%MOD;
C[0]=n;//for(int i=1;i<=n;i++) printf("%d\n",C[i]);
for(int i=0;i<LEN;i++) lnA[i]=1ll*lnA[i]*C[i]%MOD;
for(int i=0;i<LEN;i++) mA[i]=1ll*mA[i]*C[i]%MOD;
lnA=getexp(lnA,LEN);vector<int> ret=conv(lnA,mA);
int ans=ret[n-2];//printf("%d\n",ans);
for(int i=1;i<=n;i++) ans=1ll*ans*a[i]%MOD;
ans=1ll*ans*fac[n-2]%MOD;
printf("%d\n",ans);
return 0;
}
洛谷 P4002 - [清华集训2017]生成树计数(多项式)的更多相关文章
- 洛谷 P6672 - [清华集训2016] 你的生命已如风中残烛(组合数学)
洛谷题面传送门 题解里一堆密密麻麻的 Raney 引理--蒟蒻表示看不懂,因此决定写一篇题解提供一个像我这样的蒟蒻能理解的思路,或者说,理解方式. 首先我们考虑什么样的牌堆顺序符合条件.显然,在摸牌任 ...
- 洛谷 P2260 [清华集训2012]模积和 || bzoj2956
https://www.lydsy.com/JudgeOnline/problem.php?id=2956 https://www.luogu.org/problemnew/show/P2260 暴力 ...
- 洛谷P2260 [清华集训2012]模积和(容斥+数论分块)
题意 https://www.luogu.com.cn/problem/P2260 思路 具体思路见下图: 注意这个模数不是质数,不能用快速幂来求逆元,要用扩展gcd. 代码 #include< ...
- 洛谷 P6667 - [清华集训2016] 如何优雅地求和(下降幂多项式,多项式)
题面传送门 wjz:<如何优雅地 AK NOI> 我:如何优雅地爆零 首先,按照这题总结出来的一个小套路,看到多项式与组合数结合的题,可以考虑将普通多项式转为下降幂多项式,因为下降幂和组合 ...
- Loj 2320.「清华集训 2017」生成树计数
Loj 2320.「清华集训 2017」生成树计数 题目描述 在一个 \(s\) 个点的图中,存在 \(s-n\) 条边,使图中形成了 \(n\) 个连通块,第 \(i\) 个连通块中有 \(a_i\ ...
- 【UOJ#340】【清华集训2017】小 Y 和恐怖的奴隶主(矩阵快速幂,动态规划)
[UOJ#340][清华集训2017]小 Y 和恐怖的奴隶主(矩阵快速幂,动态规划) 题面 UOJ 洛谷 题解 考虑如何暴力\(dp\). 设\(f[i][a][b][c]\)表示当前到了第\(i\) ...
- Loj #2331. 「清华集训 2017」某位歌姬的故事
Loj #2331. 「清华集训 2017」某位歌姬的故事 IA 是一名会唱歌的女孩子. IOI2018 就要来了,IA 决定给参赛选手们写一首歌,以表达美好的祝愿.这首歌一共有 \(n\) 个音符, ...
- Loj #2324. 「清华集训 2017」小 Y 和二叉树
Loj #2324. 「清华集训 2017」小 Y 和二叉树 小Y是一个心灵手巧的OIer,她有许多二叉树模型. 小Y的二叉树模型中,每个结点都具有一个编号,小Y把她最喜欢的一个二叉树模型挂在了墙上, ...
- Loj #2321. 「清华集训 2017」无限之环
Loj #2321. 「清华集训 2017」无限之环 曾经有一款流行的游戏,叫做 *Infinity Loop***,先来简单的介绍一下这个游戏: 游戏在一个 \(n \times m\) 的网格状棋 ...
随机推荐
- 宙斯盾 DDoS 防护系统“降本增效”的云原生实践
作者 tomdu,腾讯云高级工程师,主要负责宙斯盾安全防护系统管控中心架构设计和后台开发工作. 导语 宙斯盾 DDoS 防护系统作为公司级网络安全产品,为各类业务提供专业可靠的 DDoS/CC 攻击防 ...
- Flask聚合函数(基本聚合函数、分组聚合函数、去重聚合函数))
Flask聚合函数 1.基本聚合函数(sun/count/max/min/avg) 使用聚合函数先导入:from sqlalchemy import func 使用方法: sun():func.sum ...
- [Java]Sevlet
0 前言 对于Java程序员而言,Web服务器(如Tomcat)是后端开发绕不过去的坎.简单来看,浏览器发送HTTP请求给服务器,服务器处理后发送HTTP响应给浏览器. Web服务器负责对请求进行处理 ...
- the Agiles Scrum Meeting 12
会议时间:2020.4.20 21:00 1.每个人的工作 今天已完成的工作 个人结对项目增量开发组: 自动评测系统基本开发完成,实现个人项目自动评测功能 issues: 个人结对功能开发组:开发自动 ...
- 问题:两个对象值相同(x.equals(y) == true),但是可能存在hashCode不同吗?
面试官的考察点 这道题仍然是考察JVM层面的基本知识,面试官认为,基本功扎实,才能写出健壮性和稳定性很高的代码. 涉及到的技术知识 (x.equals(y)==true),这段代码,看起来非常简单,但 ...
- [HNOI2009]双递增序列(洛谷P4728)+小烈送菜(内部训练题)——奇妙的dp
博主学习本题的经过嘤嘤嘤: 7.22 : 听学长讲(一知半解)--自己推(推不出来)--网上看题解--以为自己会了(网上题解是错的)--发现错误以后又自己推(没推出来)--给学长发邮件--得到正确解法 ...
- Allegro如何测量距离,测距工具的使用
http://www.allegro-skill.com/thread-2480-1-1.html
- TensorFlow从入门到入坑(1)
初识TensorFlow 一.术语潜知 深度学习:深度学习(deep learning)是机器学习的分支,是一种试图使用包含复杂结构或由多重非线性变换构成的多个处理层对数据进行高层抽象的算法. 深度学 ...
- 替换空格 牛客网 剑指Offer
替换空格 牛客网 剑指Offer 题目描述 请实现一个函数,将一个字符串中的每个空格替换成"%20".例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20A ...
- 最近公共祖先 牛客网 程序员面试金典 C++ Python
最近公共祖先 牛客网 程序员面试金典 C++ Python 题目描述 有一棵无穷大的满二叉树,其结点按根结点一层一层地从左往右依次编号,根结点编号为1.现在有两个结点a,b.请设计一个算法,求出a和b ...