uoj86 mx的组合数 (lucas定理+数位dp+原根与指标+NTT)

uoj

题目描述自己看去吧(

题解时间

首先看到 $ p $ 这么小还是质数,第一时间想到 $ lucas $ 定理。

注意 $ lucas $ 定理的另外一种写法是将数转换为 $ p $ 进制后计算$ C_{n}^{m} = \Pi C_{a_i}^{b_i} $

所以考虑对于 $ l-1 $ 和 $ r $ 各进行一次数位 $ dp $ 。

$ dp[i][j] $表示从低位起算到 $ i $ 位计算结果取模后为 $ j $ 且保证是在合法范围以内的方案数

$ dg[i][j] $ 表示从低位起算到 $ i $ 位计算结果取模后为 $ j $ 且不保证是在合法范围以内的方案数

转移方法:

对于计算到某一位 $ i $

$ n $ 已经给定,也就是说 $ b_i $ 已经确定

所以枚举 $ x $ 值在这一位对应的 $ a_i $ 设为 $ k $ ,设 $ C_{k}^{b_i}=g $

转移:

$ dg[i][jg mod p]+=dg[i-1][j] $

$ dp[i][jg mod p]+=dg[i-1][j](k

$ dp[i][jg mod p]+=dg[i-1][j](k=a_{i_{max}}) $

时间复杂度$ p^{2}logn $。

这个暴力好像是有50分。

然后考虑优化。

~~ (这么毒瘤咋考虑出来的啊) ~~

上式中的 $ jg $ 可以考虑优化掉。

这时就如毒瘤的数学题一样,我们看到p是质数,考虑直接用指标把它降维就好了。。。(啥?)

还是考虑上面的dp方程。

我们现在枚举到i位,用上面第一个转移式为例。

设 $ f[x]=\Sigma[C_{k}^{b_i}==x] $

那么转移式变成一个乘法卷积 $ dg^{'}[i]=\Sigma dg[j] * \Sigma f[g] * [jg mod p == i] $

上指标之后$ dg^{'}[i]=\Sigma dg[j] * \Sigma f[g] * [(ln[j]+ln[g]) mod \phi(p) == i] $

然后上NTT。

(markdown的公式咋回事啊空格都打不出来)

#include
using namespace std;
typedef long long lint;
typedef __int128 llint;
templateinline void read(TP &tar)
{
    TP ret=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){ret=(TP)ret*10+ch-'0';ch=getchar();}
    tar=ret*f;
}
namespace LarjaIX
{
const int N=70011,maxn=65536,P=30011,B=150;
const int mo=998244353,G=3;
lint fpow(lint a1,lint p1,lint m1);
void ntt(lint *f1,int tp);
int p,phi,len=1,g;llint n,l,r;
int rev[N];
int bitn[B],bitm[B],maxbit;
int fac[P],inv[P],facinv[P];
int ln[P];
int c[B][P];
lint ans[P];
lint f1[N],f2[N],dp[N],dg[N],dt[N];
lint wg[N],iwg[N];
void work(llint lim)
{
    memset(dp,0,sizeof(dp));
    memset(dg,0,sizeof(dg));
    memset(bitn,0,sizeof(bitn));
    for(int i=1;lim;i++) bitn[i]=lim%p,lim/=p,maxbit=max(maxbit,i);
    dp[1]=dg[1]=1;
    for(int b=1;b<=maxbit;b++)
    {
        memset(f1,0,sizeof(f1)),memset(f2,0,sizeof(f2));
        for(int i=1;i>1]>>1)|((len>>1)*(i&1));
    //-----------------------------------------------------------------------------------------------------
    //just get the g and ln of p
    {
        int tmp=phi;
        for(int i=2;i*i<=tmp;i++)if(tmp%i==0)
        {
            pri[++pri[0]]=i;
            while(tmp%i==0) tmp/=i;
        }
        if(tmp!=1) pri[++pri[0]]=tmp;
        for(int i=1;i>=1;
    }
    return ret;
}
void ntt(lint *f1,int tp)
{
    for(int i=0;i

你可能感兴趣的:(uoj86 mx的组合数 (lucas定理+数位dp+原根与指标+NTT))