LGP6620 [PUTS 2020A] 组合数问题 学习笔记

LGP6620 [PUTS 2020A] 组合数问题 学习笔记

Luogu Link

题意简述

计算 k=0nf(k)×xk×(nk)p 取模的值。

其中,f(k) 为一给定的 m 次多项式 f(k)=a0+a1k+a2k2++amkm

n,x,p109,m103

做法解析

蒟蒻不语,只是一味贺 这篇题解

666 n,x,p 仨玩意梅怡阁诗人。只有 m 的数据范围还算拟人,果断猜想正解复杂度一定是只和 m 有关的,比如 O(m2)。然而我怎么能把这一大坨式子的复杂度绑到 m 上面呢?

如果有一定的混凝土数学储备,这时候就会想到有个叫下降幂的东西。下降幂和组合数有这样一个性质:

(nk)×km=(nmkm)×nm

证明就是把两边都拆开,然后发现都消掉了。总之很优美。

然后普通多项式转下降幂多项式用到斯特林数,这玩意刚好是 O(M2) 的。这启示我们要做出来这题下降幂和斯特林数必不可少。

假设我们成功把 f(k) 转成了下降幂多项式,那么就有:

i=0mbiki×xk×(nk)=i=0mbixk(niki)ni

则原式可化为:

k=0ni=0mbixk(niki)ni=i=0mbinik=0n(niki)xk

发现 k 枚举到 i 以下是没有意义的,于是再把式子化一下得到

i=0mbinik=in(niki)xk=i=0mbinik=0ni(nik)xk+i

再把 xi 提出来得到:

i=0mbinixik=0ni(nik)xk

啪一下很快啊!我们发现里面那玩意似乎就是个二项式定理的样子!
如果看不出来的话我们给 ni 换元为 t

k=0t(tk)xt=(x+1)t

这甚至还是 b=1 的特殊形式下的式子。

好好好,现在原式就可以化为

i=0mbinixi(x+1)ni

  • 好了,还有什么人,要提问?
  • 我要。
  • 他来了。
  • 我算普通多项式转下降幂多项式,好吗?
  • ao

普通多项式转下降幂多项式这部分就是混凝土数学基本功了。直接上柿子,由:

xn=i=0n{ni}xi

可推得:

i=0maiki=i=0maij=0ixj=i=0mkij=im{ji}aj

(这一步转换有点难度,这么理解:显然在中式中,xji[j,m] 的时候被算了)

也就是说 bi=j=im{j i}aj

斯特林数 O(M2) 递推。
啊啊这道题到此终于做完了。

最后我们算的柿子就是:

i=0m((j=im{ji}aj)×nixi(x+1)ni)

这道题就做完了!

代码实现

题目并不保证 p 是质数,所以不能用费马小定理快速幂求逆元!我在这卡了一个小时 QAQ

#include <bits/stdc++.h>
using namespace std;
namespace obasic{
    typedef long long lolo;
    template <typename _T>
    void readi(_T &x){
        _T k=1;x=0;char ch=getchar();
        for(;!isdigit(ch);ch=getchar())if(ch=='-')k=-1;
        for(;isdigit(ch);ch=getchar())x=(x<<3)+(x<<1)+ch-'0';
        x*=k;return;
    }
    template <typename _T>
    void writi(_T x){
        if(x<0)putchar('-'),x=-x;
        if(x>9)writi(x/10);
        putchar(x%10+'0');
    }
};
using namespace obasic;
const int MaxM=1e3+5;
lolo N,X,P,M,A[MaxM],B[MaxM];
lolo ssn[MaxM][MaxM],fans,xpo[MaxM];
namespace omathe{
    lolo fastpow(lolo a,lolo b,lolo p){
        lolo res=1;
        for(;b;(a*=a)%=p,b>>=1)if(b&1)(res*=a)%=p;
        return res;
    }
    lolo getinv(lolo a,lolo p){
        return fastpow(a,p-2,p);
    }
};
using namespace omathe;
int main(){
    readi(N),readi(X),readi(P),readi(M);
    for(int i=0;i<=M;i++)readi(A[i]);
    ssn[0][0]=1;
    for(int i=1;i<=M;i++){
        for(int j=1;j<=M;j++){
            ssn[i][j]=(ssn[i-1][j-1]+j*ssn[i-1][j]%P)%P;
        }
    }
    xpo[M]=fastpow(X+1,N-M,P);
    for(int i=M-1;~i;i--)xpo[i]=xpo[i+1]*(X+1)%P;
    lolo cans,tmp1=1,tmp2=1;
    for(int i=0;i<=M;(tmp1*=(N-i))%=P,(tmp2*=X)%=P,i++){
        for(int j=i;j<=M;j++)(B[i]+=ssn[j][i]*A[j]%P)%=P;
        cans=B[i]*tmp1%P*tmp2%P*xpo[i]%P,(fans+=cans)%=P;
    }
    writi(fans);
    return 0;
}

反思总结

但是,为什么这道题要这么思考呢?

笔者和同机房巨佬 w9095 进行了这样的交流:

我:巨佬,你有什么对这道题更高妙的理解吗?我感觉这道题的思考过程好像就是因为 m 小然后就套路想到斯特林数之类的……
w9095:呃是这样的:据我所知,我在OI里碰见高次项(本题中就是 xk)一般就两种想法,要么把它留在那,用某种方式预处理掉(线性筛、反演等等);要么直接让它滚蛋,用斯特林数转下降幂,然后这玩意有很好的组合性质。
我:所以说在化下降幂、柿子推到那前你也看不出来那个二项式定理形式是吧。
w9095:是的,就是式子推到那了,然后发现其“恰好”满足二项式定理的形式。

posted @   矞龙OrinLoong  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
点击右上角即可分享
微信分享提示