「2021Feb」计数杂烩和杂题选做

生成函数

基础 EGF

一个数列的指数型生成函数为 (sumlimits_{i=0}^{infty} frac{a_i}{i!}x^i)

拿出两种相同的物品(种内相同)(i) 个,求不同排列的方案数

[G(x)=sumlimits_{i=0}^{infty}frac{x^i}{i!} ]

[R(x)=sumlimits_{i=0}^{infty}frac{res_i}{i!}x^i ]

[R(x)=G^2(x) ]

数学意义理解一下

例题还没有

prufer序列

序列 ( o) 树:每次找在 ({1dots n}) 点集中还有的最小且没有在序列里面出现过的点和序列里面第一个点连边

( o) 序列:当前树上度数为 (1) 的编号最小的点和所连的点加入序列

性质:

((1)) (prufer) 序列长度为 (n-2)

((2)) 度数为 (d_i) 的点在序列里面出现了 (d_i-1)

((3)) (n) 个点 (m) 个联通块每个大小为 (a_i) 的方案是:((n^{m-2})prod a_i)

((4)) (n) 个点钦定 (k) 个点在不同的树里面的方案数是 (k imes n^{n-k-1})

最后一个不会证明

序列和 (EGF) 的混用是数树题的常见套路

例题

清华集训2017 生成树计数

法一:

题目中枚举树的操作是很不可做的,那么考虑转成序列上的计数问题

(prufer) 序列上钦定若干个点值,那么就变成了所有联通块的 (d_i) 之和为 (n-2)

[sum_{D} frac{(n-2)!}{prod (d_i!)} a_i^{d_i+1} (d_i+1)^{2m} (prod_{j eq i} (d_j+1)^m a_j^{d_j+1}) ]

价值不同,所以是排列问题,所以 (prufer) 序列上使用指数型生成函数就是设

[F(fx)=sum A_i(x)prod B_i(x) ]

[A_i(x)=sum_{d} frac{a_i^{d+1} x^d}{d!}(d+1)^{2m} ]

[B_i(x)=sum_{d} frac{a_i^{d+1}x^d}{d!} (d+1)^{m} ]

关注到系数多 (1),所以积一下分,再用斯特林数换掉 (d^k),再接着把后面的关于 (a_i^dfrac{x^d}{d!})(Tyler) 展开缩起来

最后求导得到:

[A_i(x)=e^{a_ix}sum_{j=0} ^{2m} egin{Bmatrix} 2m+1\ j+1 end{Bmatrix}a_i^{j+1} x_j ]

[B_i(x)=e^{a_ix}sum_{j=0} ^{m} egin{Bmatrix} m+1\ j+1 end{Bmatrix}a_i^{j+1} x_j ]

(F(x)) 的过程需要分治 (NTT)

这个方法统计答案的时候需要把 (Tyler) 展开还原成多项式的形式

啥时候我才能这么熟练把各种东西换来换去呀……

现在是看半天都能懂,但是自己推到处是瓶颈……

    struct poly{
        vector<int> A,B;
        inline void init(){A.clear(); B.clear(); return ;}
    };
    const int SZ=4.2e6;
    int T1[SZ],T2[SZ],T3[SZ],n,m,ans;
    int dd=0;
    inline poly merge(poly a,poly b){
        poly ans; ans.init();
        int lim=1,len=max(a.A.size()+b.B.size(),a.B.size()+b.A.size());
        while(lim<len-1) lim<<=1;
        for(reg int i=0;i<lim;++i) T1[i]=T2[i]=T3[i]=0;
        for(reg int i=0;i<lim;++i) T1[i]=(i<a.A.size())?a.A[i]:0; NTT(T1,lim,1); 
        for(reg int i=0;i<lim;++i) T2[i]=(i<b.A.size())?b.A[i]:0; NTT(T2,lim,1);
        for(reg int i=0;i<lim;++i) T3[i]=mul(T1[i],T2[i]); NTT(T3,lim,-1); 
        int tsz=min(a.A.size()+b.A.size()-1,n); ans.A.resize(tsz); for(reg int i=0;i<tsz;++i) ans.A[i]=T3[i];
        for(reg int i=0;i<lim;++i) T3[i]=(i<a.B.size())?a.B[i]:0; NTT(T3,lim,1); 
        for(reg int i=0;i<lim;++i) T3[i]=mul(T3[i],T2[i]); NTT(T3,lim,-1); 
        tsz=min(n,len-1); ans.B.resize(tsz); for(reg int i=0;i<tsz;++i) ans.B[i]=T3[i]; 
        for(reg int i=0;i<lim;++i) T3[i]=(i<b.B.size())?b.B[i]:0; NTT(T3,lim,1);
        for(reg int i=0;i<lim;++i) T3[i]=mul(T3[i],T1[i]); NTT(T3,lim,-1); 
        for(reg int i=0;i<tsz;++i) ans.B[i]=add(ans.B[i],T3[i]);
        return ans;
    }
    inline poly solve(int l,int r){
        if(l==r){
            poly ans; ans.init(); ans.A.resize(m+1); ans.B.resize(2*m+1);
            for(reg int now=a[l],i=0;i<(m<<1|1);++i,now=mul(now,a[l])){
                if(i<=m) ans.A[i]=mul(now,s[m+1][i+1]);
                ans.B[i]=mul(now,s[m<<1|1][i+1]);
            } return ans;   
        } int mid=(l+r)>>1;
        return merge(solve(l,mid),solve(mid+1,r));
    }
    signed main(){
        n=read(); m=read(); for(reg int i=1;i<=n;++i) S=add(S,a[i]=read());
        fac[0]=fac[1]=inv[0]=inv[1]=ifac[0]=ifac[1]=1; 
	for(reg int i=2;i<N;++i){
		fac[i]=mul(fac[i-1],i);
		inv[i]=mod-mul(mod/i,inv[mod%i]);
		ifac[i]=mul(ifac[i-1],inv[i]); 
	}s[0][0]=1; 
	for(reg int i=1;i<=2*m+1;++i) 
	for(reg int j=1;j<=2*m+1;++j) s[i][j]=add(mul(s[i-1][j],j),s[i-1][j-1]); 
	poly res=solve(1,n);
    	p[0]=1; for(reg int i=1;i<=n;++i) p[i]=mul(p[i-1],S); 
	for(reg int i=0;i<=n-2;++i) ans=add(ans,mul(res.B[i],mul(p[n-2-i],ifac[n-2-i]))); 
	printf("%lld
",mul(ans,fac[n-2])); 
    	return 0;
    }
}

法二

(prufer) 序列后面添加 (N) 个数,使得度数和出现次数相同

那么考虑上述式子的现实意义:(这步确实很厉害,很难想到)

(prufer) 序列上用 (m) 个颜色给序列涂色,每次选择一个数,在其任意位置涂 (m)

再任意选择一个数(不要求不同),涂色 (m)

最后将涂色方案数乘上每个数的权值 (a_i^{d_i}) 累加

那么考虑 (f_{i,j}) 为仅考虑前面的项,前 (i) 个位置中, (N-2) 个数里面有 (j) 个涂色,(g_{i,j}) 为考虑整个式子,其余和 (f) 一样

用分治 (NTT) 优化转移

「国家集训队2011」 lqp的整数拆分

门槛太高,和题目来源挺契合的

读一下 ( exttt{具体数学}) 就得到了 斐波那契数的生成函数的通项公式

(其实也不难,按照递推式解二次方程就行了,和上面的题目类似)

[Fib(x)=frac{1}{1-x-x^2} ]

写个 (dp) 发现这题其实是把答案数组 (G(x))(F(x)) 做了卷积,也就是说:(G(x)=F(x)G(x)+1)

带入得到 (G(x)) 的式子为 (frac{x}{1-2x-x^2}+1)

将底下的部分分解因式然后设未知量大力解方程就行了

[ans=-frac{sqrt2}{4}(1-sqrt 2)^n+frac{sqrt2}{4}(1+sqrt 2)^n ]


(sqrt 2mod 10^9+7) 是二次剩余,类似 (x^2equiv nmod p)(n) 都是二次剩余

二次剩余的一些性质就是 (p) 为奇质数的时候满足有 (frac{p-1}2)(n) 使得该方程有解,其充分必要条件为 (n^{frac{p-1}2}equiv 1mod p)

其实 (n^{frac{p-1}2}mod p) 的另外一个是得数是 (p-1),用相关知识比较容易可以得到

「Codeforces438E」 The Child and Binary Tree

列出来 (dp) 式子之后发现是三元卷积,考虑写成生成函数的形式得到一个关于 (F) 的一元二次方程

那么根据是不是存在多项式的逆元就拉板子过来就行

城市规划

同下,考虑每个联通块的大小和连边方式得到 (dp) 转移式

推成的两个多项式卷积的形式接着做就成了

射名丸文的笔记

完全践行了总量计数的方式:

考虑 (n) 个点的竞赛图的 (Hamilton) 回路的总数为 ((n-1)! imes 2^{inom n 2-(n-1)})

含义就是排列是 (frac{n!} n) 个,同时剩下的点随便连得到

剩下需要考虑有多少图没有 (Hanmilton) 回路

有一个结论是如果有向图强联通,那么其必然有 (Hanmilton) 回路

那么容斥掉不强联通的图的数量,设强联通的为(i) 点竞赛图数量为 (f_i)

容斥的过程也就是减掉极大的强联通联通子图,剩下的随便连

[f_i=2^{inom i2 }-sumlimits_{j=1}^{i-1} 2^{inom {i-j}2}inom ijf_j ]

(f_i) 的式子挪到一侧,得到:

[sum_{j=1}^{i} 2^{inom {i-j} 2} f_jinom ij=2^{inom i 2} ]

多除几次阶乘,就能变成分治 (NTT) 的形式

但是要追求更好的复杂度,那么考虑退成求逆的模型,也就是说,对于满足

[F(x)=sum_{j=0}^{i-1} F(j)G(x-j) ]

的多项式 (F,G),构造 (OGF) 之后卷积发现卷成了 (F)(OGF),那么

[F(x)=frac{G(x)-G(0)}{G(x)} ]

求逆即可

PKUWC2018 猎人杀

先转化一下题目,变成如果没打到就继续打,简化一点计算

正难则反,设剩下没死的集合为 ({S})

按照等比数列求和公式,对于一个集合 (S) 的概率应该是

[p(S)=sum_{i=0}^{infty} [frac{sum-a_1-Sum(S)} {sum}]^i frac{a_1} {sum} ]

考虑封闭形式得到

[p(S)=frac{a_1}{Sum(S)+a_1} ]

其对答案的贡献为 ((-1)^{|S|})

观察奇怪的数据范围发现这个题目 (nle 1e5)

所以转换枚举方式:枚举 (Sum(S))

[ans=sum_{i=1}^{Sum o w} [sum_{Sum(S)=i} (-1)^{|S|}] frac{a_1}{i+a_1} ]

前面的东西直觉上又是子集和,那么根据 (FWT) 是多元生成函数的思路,生成函数求积即可

直接卷显然不行,考虑 ”分治乘法“

这个分治乘法的时候数组可以考虑用“垃圾桶”来实现可行转化,挺 (trivial)

TJOI2019 唱跳rap和篮球

一个小误区是放置 (i)(4) 人组的方案是 (inom{n-3i}{i})

整体计算即可,那么容斥就完事了,不清楚为啥这俩题是真的套路

原文地址:https://www.cnblogs.com/yspm/p/14358965.html