zoukankan      html  css  js  c++  java
  • 【2020省选Day1T2】 LOJ3300 「联合省选 2020 A」组合数问题

    题目链接

    解法一:递推求导,搞定k^i

    前置知识

    求导法则

    基本法则:((x^k)'=kx^{k-1})

    四则运算:

    • ((f(x)+g(x))'=f'(x)+g'(x))
    • ((f(x)cdot g(x))'=f'(x)g(x)+f(x)g'(x))

    用乘法法则,可以推出一个常数乘以一个函数的求导法则,即:((ccdot f(x))'=0cdot f(x)+ccdot f'(x)=ccdot f'(x))。然后,对于减法,就可以看做加(-1cdot g(x)),直接套用加法法则即可得到:((f(x)-g(x))'=f'(x)-g'(x))

    复合函数:((f(g(x)))'=f'(g(x))cdot g'(x))

    对于除法,可以看做乘以(frac{1}{g(x)}),也就是(h(x)=x^{-1})(g(x))的复合函数。于是得到:(displaystyle left(frac{f(x)}{g(x)} ight)'=f'(x)cdotfrac{1}{g(x)}+f(x)cdot(-g^{-2}(x)cdot g'(x))=frac{f'(x)g(x)-f(x)g'(x)}{g^2(x)})

    应用:对一个多项式函数,(F(x)=sum_{i=0}^{n}f_ix^i)求导,可以结合“基本法则”和“加法法则”,得到:(F'(x)=sum_{i=0}^{n}f_{i}cdot icdot x^{i-1}=sum_{i=0}^{n-1}f_{i+1}(i+1)x^i)

    二项式定理

    [(x+y)^{n}=sum_{i=0}^{n}{nchoose i}x^iy^{n-i} ]

    证明略。

    本题用到它的一种特殊形式,就是((x+1)^{n}=sum_{i=0}^{n}{nchoose i}x^i)。你可以理解为(y=1)的情况。

    题解

    考虑(m=0)的subtask,答案就是:(a_0cdot sum_{k=0}^{n}x^k{nchoose k})。容易发现这就是上面讲的“二项式定理的特殊形式”,所以它等于(a_0cdot(x+1)^n)

    这给我们一个启发,就是可以把(f(k))的每一项拿出来分别计算。于是原式就等于:

    [sum_{i=0}^{m}a_isum_{k=0}^{n}k^ix^k{nchoose k} ]

    我们设(F_i(x)=sum_{k=0}^{n}k^ix^k{nchoose k})。那么原式就等于(sum_{i=0}^{m}a_iF_i(x))。发现(F_i(x))里,比较令人头疼的,就是这个(k^i)。怎么处理它呢?本节介绍的“求导”,就是一种很好的方法。

    对一个多项式函数,(T(x)=sum_{k=0}^{n}t_kx^k),考虑把它每一项分别都乘以(k),得到( ext{newT}(x)=sum_{k=0}^{n}t_kcdot kcdot x^k)。发现这个是什么呢?这个就是把(T'(x))每项都乘以(x)。也就是说:( ext{newT}(x)=xcdot T'(x))

    回到本题,我们知道,(F_i(x)),就相当于把(F_{i-1}(x))的每一项分别都乘以(k)。所以,(F_i(x)=xcdot F_{i-1}'(x))。也就是先求导,再乘以(x)。例如,(F_0(x)=(x+1)^{n}),那么就有:(F_1(x)=x((x+1)^n)')。带到原式里,也可以验证出来(把每一项都提一个(x)出来即可)。

    那么我们就可以依次递推(F_1(x),F_2(x),dots ,F_m(x))。也就是先对(F_{i-1}(x))求导,再把所有项都乘以(x)。例如我多写几个:

    • (F_0(x)=(x+1)^{n})
    • (F_1(x)=x((x+1)^n)'=ncdot xcdot (x+1)^{n-1})
    • (F_2(x)=x(ncdot xcdot (x+1)^{n-1})'=ncdot xcdot (x+1)^{n-1}+n(n-1)cdot x^2cdot (x+1)^{n-2})
    • $dots $

    你可以发现,它有很多项。每一项,都可以写成(d_jcdot x^{j}cdot (x+1)^{n-j})的形式。其中,(d_j)是一个常数。(0leq jleq m)。更具体来说:

    [F_i(x)=sum_{j=0}^{m}d_{i,j}x^j(x+1)^{n-j} ]

    这个(d)数组,我们递推一下,可以(O(m^2))求出。然后预处理一下(x)((x+1))的次幂,就可以直接计算答案了。

    (d)数组怎么递推?首先根据导数的加法法则,我们要对(F_{i-1}(x))求导,就先对它的每一项求导,再加起来。考虑某一项(d_{i-1,j}x^{j}(x+1)^{n-j}),它是一个乘法(前面的常数不算,是(x^j)((x+1)^{n-j})两个东西相乘)。要对它求导,就用到导数的乘法法则:((f(x)cdot g(x))'=f'(x)g(x)+f(x)g'(x))。所以(F_{i-1}(x))里的每一项,会对(F_{i}(x))里的两项有贡献。当然,别忘了对(F_{i-1}(x))求导后,还要把结果再乘以(x)。具体地也可以写成:

    [d_{i,j}=d_{i-1,j}cdot j+d_{i-1,j-1}cdot(n-(j-1)) ]

    时间复杂度(O(m^2))

    参考代码(在LOJ查看):

    //problem:LOJ3300
    #include <bits/stdc++.h>
    using namespace std;
    
    #define pb push_back
    #define mk make_pair
    #define lob lower_bound
    #define upb upper_bound
    #define fi first
    #define se second
    #define SZ(x) ((int)(x).size())
    
    typedef unsigned int uint;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int,int> pii;
    
    const int MAXM=1000;
    int MOD;
    inline int mod1(int x){return x<MOD?x:x-MOD;}
    inline int mod2(int x){return x<0?x+MOD:x;}
    inline void add(int& x,int y){x=mod1(x+y);}
    inline void sub(int& x,int y){x=mod2(x-y);}
    inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}
    
    int n,x,m,a[MAXM+5],d[MAXM+5][MAXM+5],pow1[MAXM+5],pow2[MAXM+5];
    
    int main() {
    	cin>>n>>x>>MOD>>m;x%=MOD;
    	for(int i=0;i<=m;++i)cin>>a[i],a[i]%=MOD;
    	d[0][0]=1;
    	for(int i=1;i<=m;++i){
    		for(int j=0;j<=m;++j){
    			// d[i][j] * x^{j} * (x+1)^{n-j}
    			d[i][j]=(ll)d[i-1][j]*j%MOD;
    			if(j>0)add(d[i][j],(ll)d[i-1][j-1]*(n-(j-1))%MOD);
    		}
    	}
    	pow1[0]=pow_mod(x+1,n-m);
    	for(int i=1;i<=m;++i)pow1[i]=(ll)pow1[i-1]*(x+1)%MOD;
    	pow2[0]=1;
    	for(int i=1;i<=m;++i)pow2[i]=(ll)pow2[i-1]*x%MOD;
    	
    	int ans=0;
    	for(int i=0;i<=m;++i){
    		for(int j=0;j<=m;++j){
    			add(ans,(ll)a[i]*d[i][j]%MOD*pow2[j]%MOD*pow1[m-j]%MOD);
    		}
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

    解法二:用组合恒等式,拆出斯特林数

    前置知识

    一个组合恒等式

    [k imes {nchoose k}=n imes {n-1choose k-1} ]

    证明:

    把两边按定义展开:

    左边(displaystyle=kcdot frac{n!}{k!(n-k)!})

    右边(displaystyle=ncdot frac{(n-1)!}{(k-1)!(n-k)!})

    两边同时乘以(frac{1}{k}),都等于(frac{n!}{k!(n-k)!})

    所以左边(=)右边。

    下降幂的定义

    [a^{underline{b}}=a(a-1)dots (a-b+1) ]

    题解

    根据上一种方法的初步转化,原式可以写成:

    [sum_{i=0}^{m}a_isum_{k=0}^{n}k^ix^k{nchoose k} ]

    (F_i(x)=sum_{k=0}^{n}k^ix^k{nchoose k})。那么原式就等于(sum_{i=0}^{m}a_iF_i(x))。还是考虑求(F_i(x))。我们的重点是要把(k^i)消掉,剩余的部分,如果是一个(x^k)再乘一个组合数,可以直接套二项式定理。因为有了前面的那个组合恒等式,所以现在思路比较明确:先保留(x^k)不要动,拿组合数去和(k^i)消,消出一个新的组合数加一个系数,我们就赢了。

    我们先考虑(igeq1)的情况。

    (i=1)时,用我们前面写的组合恒等式,可以写成:

    [egin{align} F_1(x)&=sum_{k=0}^{n}kcdot x^kcdot {nchoose k}\ &=sum_{k=0}^{n}x^kcdot ncdot{n-1choose k-1} end{align} ]

    (i=2)时,也可以推:

    [egin{align} F_2(x)&=sum_{k=0}^{n}k^2cdot x^kcdot {nchoose k}\ &=sum_{k=0}^{n}x^kcdot kcdot ncdot{n-1choose k-1}\ &=sum_{k=0}^{n}x^kcdot (1+(k-1))cdot ncdot{n-1choose k-1}\ &=sum_{k=0}^{n}x^kleft(n{n-1choose k-1}+(k-1)cdot n{n-1choose k-1} ight)\ &=sum_{k=0}^{n}x^kleft(n{n-1choose k-1}+n(n-1){n-2choose k-2} ight) end{align} ]

    其中,最重要的一步,是我们把一个(k)拆成了((1+(k-1)))。这很巧妙。其他的地方都是在套那个组合恒等式。

    看最后推出的结果,发现,每个组合数前,是一个(n)的下降幂。所以我们盲猜:(F_i(x)=sum_{k=0}^{n}x^ksum_{j=1}^{i}n^{underline{j}}{n-jchoose k-j})

    很遗憾,这个猜想不对。

    事实上,在(n^{underline{j}}{n-jchoose k-j})前,还需要加一些系数。只不过对于(F_1(x))(F_2(x))来说,这个系数恰好都是(1)

    我们继续考虑(i=3)的情况。

    ps:因为公式太大搞成图片了,如果没加载出来,就自己点一下链接吧。https://cdn.luogu.com.cn/upload/image_hosting/fg2qsqu3.png

    发现没有,现在,后面的系数变成了(1 3 1)。不全是(1)了。

    容易发现,(F_i)后面的这一坨东西,恰好有(i)项。如果设第(j)项前面的系数为(s(i,j)),则可以写成:

    [F_i(x)=sum_{k=0}^{n}x^ksum_{j=1}^{i}s_{i,j}cdot n^{underline{j}}{n-jchoose k-j} ]

    考虑如何求这个(s_{i,j})。首先,(F_{i-1}(x))后面的东西里,每一项都要乘以(k)。例如,(s_{i-1,j}cdot n^{underline{j}}{n-jchoose k-j}),乘以(k),变成(kcdot s_{i-1,j}cdot n^{underline{j}}{n-jchoose k-j})。然后,我们会把(k),拆成((j+(k-j)))。所以(s_{i-1,j})会对(s_{i,?})里的两个地方产生贡献。一个是(j)产生的,贡献到(s_{i,j}),贡献系数为(j)。另一个是((k-j))产生的,贡献到(s_{i,j+1}),贡献系数为(1)

    那反过来说,就可以得到:

    [s_{i,j}=s_{i-1,j}cdot j+s_{i-1,j-1} ]

    其中,边界是(s_{1,1}=1)

    于是就可以(O(m^2))递推求出整个(s)数组。顺便说一句,这里的(s),其实就是第二类斯特林数,不过有没有看出来都不影响解题。

    现在求出了(s),再回头看(F_i(x))。我们按一开始说的思路,用二项式定理,把(x^k)和组合数搞到一起去。具体来说:

    [egin{align} F_i(x)&=sum_{j=1}^{i}s_{i,j}cdot n^{underline{j}}sum_{k=0}^{n}x^k{n-jchoose k-j}\ &=sum_{j=1}^{i}s_{i,j}cdot n^{underline{j}}sum_{k=0}^{n-j}x^{k+j}{n-jchoose k}\ &=sum_{j=1}^{i}s_{i,j}cdot n^{underline{j}}cdot x^jsum_{k=0}^{n-j}x^k{n-jchoose k}\ &=sum_{j=1}^{i}s_{i,j}cdot n^{underline{j}}cdot x^j(x+1)^{n-j}\ end{align} ]

    以上是(igeq 1)的情况。当(i=0)时,显然有(F_0=(x+1)^n)。当然,你也可以认为,(s_{0,0}=1)(n^{underline{0}}=1)(s_{i,0}=0) ((igeq 1))。然后,你把(j)改成从(0)开始循环,也是对的。这样就不用特判(i=0)的情况。

    然后答案就是(sum_{i=0}^{m}a_iF_i(x))

    时间复杂度(O(m^2))

    参考代码,他鸽了

    两种解法的比较与联系

    事实上,推到最后,大家都能看出来,第一种解法里的(d_{i,j}),就是第二种解法里的(s_{i,j}cdot n^{underline{j}})

    两种解法,都想消掉(k^i)。他们走的路径不同。

    • 第一种解法,比较鲁莽。你不是有一个(k^i)吗,我对着你硬消。求一次导干掉一个(k)。这种“鲁莽”的背后,是有“求导”这个强大的工具在支持。
    • 第二种解法,看起来稍微圆滑一点。我先让组合数({nchoose k})去和你消。用的就是一个组合恒等式。然后等你(k^i)没了,我再把剩下的组合数和(x^k)打包成一个二项式定理。
  • 相关阅读:
    GridView中实现可收缩的面板
    android之xml数据解析(Pull)
    android之xml数据解析(DOM)
    android intent 传递list或者对象
    Android之单元测试
    Directx11教程(48) depth/stencil buffer的作用
    Directx11教程(47) alpha blend(4)雾的实现
    Directx11教程41 纹理映射(11)
    Directx11教程40 纹理映射(10)
    Directx11教程(46) alpha blend(3)
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/13196203.html
Copyright © 2011-2022 走看看