UOJ #86 mx的组合数 (数位DP+NTT+原根优化)
Posted guapisolo
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UOJ #86 mx的组合数 (数位DP+NTT+原根优化)相关的知识,希望对你有一定的参考价值。
matthew99神犇的题解讲得非常清楚明白,跪烂Orzzzzzzzzzzzzz
总结一下,本题有很多重要的突破口
1.Lucas定理
看到n,m特别大但模数特别小时,容易想到$lucas$定理
$C_{n}^{m}=C_{n/p}^{m/p}cdot C_{n;mod;p}^{m;mod;p};(mod;p)$
但普通的$lucas$显然不适用于多次计算,我们可以把$lucas$定理展开
我们把$n$和$m$都看成两个$p$进制数$a$和$b$
$C_{n}^{m}=pi C_{a_{i}}^{b_{i}};(mod;p)$
这个展开显然成立
2.数位$DP$
想到了上一条进制转化,很容易就联想到数位$DP$
定义$f[i][j]$表示枚举到第$i$位,余数是$j$的方案数
转移十分经典,设现在要填上的数是$x$,$0$表示没达到上界,$1$达到上界,设$z=C_{x}^{b_{i+1}}$
$f0[i][jcdot z;mod;p]+=f0[i][j]$
$x<a_{i+1}$时,$f0[i][jcdot z;mod;p]+=f1[i][j]$
$x=a_{i+1}$时,$f1[i][jcdot z;mod;p]+=f1[i][j]$
总复杂度$O(p^2log_{p}n)$
3.原根优化与多项式乘法
上面的$p^{2}$转移咋这么无脑呢,是不是有啥优化啊?是的
由于$p$是一个质数,它一定存在一个原根$g$
这就要涉及到离散对数了。其实这是一个映射
$g^{ind(x)}equiv x;(mod;p)$
$ind(x);(ind(x)in[0,p-1))$与$x;(xin [1,p))$是一组一一映射
那么$jcdot z;mod;p$
$=g^{ind(j)+ind(z);(mod;p-1)};mod;p$
我们利用映射关系,把底数化成指数
这样转移变成了卷积的形式,用多项式乘法每次$O(plogp)$计算
计算出结果后,再逆映射回来得到实际的答案
而离散对数的映射并不能处理余数等于$0$的情况,我们每次$O(p)$单独讨论即可
总时间$O(plogplog_{p}n)$
1 #include <bits/stdc++.h> 2 #define N1 (1<<16)+10 3 #define dd double 4 #define ld long double 5 #define ll long long 6 #define uint unsigned int 7 #define i128 __int128 8 using namespace std; 9 10 const int inf=0x3f3f3f3f; 11 i128 gi128() 12 { 13 i128 ret=0;int fh=1;char c=getchar(); 14 while(c<‘0‘||c>‘9‘){if(c==‘-‘)fh=-1;c=getchar();} 15 while(c>=‘0‘&&c<=‘9‘){ret=ret*10+c-‘0‘;c=getchar();} 16 return ret*fh; 17 } 18 ll qpow(ll x,ll y,const int &p) 19 { 20 ll ans=1; 21 for(;y;x=x*x%p,y>>=1) if(y&1) ans=ans*x%p; 22 return ans; 23 } 24 const ll p=998244353; 25 namespace NTT{ 26 ll a[N1],b[N1],c[N1],Wn[N1],_Wn[N1]; 27 int r[N1]; 28 ll qpow(ll x,ll y) 29 { 30 ll ans=1; 31 for(;y;x=x*x%p,y>>=1) if(y&1) ans=ans*x%p; 32 return ans; 33 } 34 void NTT(ll *s,int len,int type) 35 { 36 int i,j,k; ll wn,w,t; 37 for(i=0;i<len;i++) if(i<r[i]) swap(s[i],s[r[i]]); 38 for(k=2;k<=len;k<<=1) 39 { 40 wn=(type>0)?Wn[k]:_Wn[k]; 41 for(i=0;i<len;i+=k) 42 { 43 for(j=0,w=1;j<(k>>1);j++,w=w*wn%p) 44 { 45 t=w*s[i+j+(k>>1)]%p; 46 s[i+j+(k>>1)]=(s[i+j]+p-t)%p; 47 s[i+j]=(s[i+j]+t)%p; 48 } 49 } 50 } 51 } 52 void Pre(int len,int L) 53 { 54 int i; 55 for(i=0;i<len;i++) r[i]=(r[i>>1]>>1)|((i&1)<<(L-1)); 56 for(i=1;i<=len;i<<=1) Wn[i]=qpow(3,(p-1)/i), _Wn[i]=qpow(Wn[i],p-2); 57 } 58 void Main(int len) 59 { 60 int i,invl=qpow(len,p-2); 61 NTT(a,len,1); NTT(b,len,1); 62 for(i=0;i<len;i++) c[i]=a[i]*b[i]%p; 63 NTT(c,len,-1); 64 for(i=0;i<len;i++) c[i]=c[i]*invl%p; 65 memset(a,0,sizeof(a)); memset(b,0,sizeof(b)); 66 } 67 }; 68 69 int T,m,G; 70 i128 n,l,r; 71 int a[N1],b[N1],tmp[N1]; 72 ll f0[2][N1],f1[2][N1],mul[N1],_mul[N1],ans[N1]; 73 74 inline ll C(int N,int M) 75 { 76 if(M>N) return 0; 77 return mul[N]*_mul[M]%m*_mul[N-M]%m; 78 } 79 80 int pr[N1],use[N1],ind[N1],_ind[N1],son[N1]; 81 void get_ind() 82 { 83 int i,j,ns=0,flag,x,cnt=0,sz=0; 84 for(i=2;i<=m-1;i++) 85 { 86 if(!use[i]) pr[++cnt]=i; 87 for(j=1;j<=cnt&&i*pr[j]<=m-1;j++) 88 { 89 use[i*pr[j]]=1; 90 if(i%pr[j]==0) break; 91 } 92 } 93 for(j=1;j<=cnt;j++) if((m-1)%pr[j]==0) son[++sz]=(m-1)/pr[j]; 94 for(i=2;i<=m-2;i++) 95 { 96 flag=1; 97 for(j=1;j<=sz;j++) if(qpow(i,son[j],m)==1){ flag=0; break;} 98 if(flag){ G=i; break; } 99 } 100 ind[1]=0; _ind[0]=1; ind[0]=m-1; 101 for(i=1,x=1;i<=m-2;i++) x=x*G%m, ind[x]=i, _ind[i]=x; 102 } 103 //using NTT::a; using NTT::b; using NTT::c; 104 void solve(i128 w,int type) 105 { 106 int i,j,k,nw=0,nn=0,len,L,now=0,pst=1; 107 if(w<n){ ans[0]=((w+1)*type%p+ans[0]+p)%p; return; } 108 while(w>0) nw++,tmp[nw]=w%m,w/=m; 109 for(i=1;i<=nw;i++) a[nw-i+1]=tmp[i]; 110 i128 N=n; 111 while(N>0) nn++,tmp[nn]=N%m,N/=m; 112 for(i=1;i<=nn;i++) b[nw-i+1]=tmp[i]; 113 114 for(len=1,L=0;len<m+m-1;len<<=1,L++); 115 NTT::Pre(len,L); 116 117 memset(f0,0,sizeof(f0)); memset(f1,0,sizeof(f1)); 118 for(j=0;j<a[1];j++) f0[1][C(j,b[1])]++; 119 f1[1][C(a[1],b[1])]++; 120 for(i=1;i<nw;i++) 121 { 122 memset(f0[now],0,sizeof(f0[now])); memset(f1[now],0,sizeof(f1[now])); 123 124 for(j=1;j<m;j++) NTT::a[ind[j]]=f0[pst][j]; 125 for(j=0;j<m;j++) NTT::b[ind[C(j,b[i+1])]]++; 126 for(j=1;j<m;j++) (f0[now][0]+=f0[pst][j]*NTT::b[m-1])%=p; 127 (f0[now][0]+=f0[pst][0]*m)%=p; NTT::b[m-1]=0; 128 NTT::Main(len); 129 for(j=0;j<len;j++) (f0[now][_ind[j%(m-1)]]+=NTT::c[j])%=p; 130 131 for(j=1;j<m;j++) NTT::a[ind[j]]=f1[pst][j]; 132 for(j=0;j<a[i+1];j++) NTT::b[ind[C(j,b[i+1])]]++; 133 for(j=1;j<m;j++) (f0[now][0]+=f1[pst][j]*NTT::b[m-1])%=p; 134 (f0[now][0]+=f1[pst][0]*a[i+1])%=p; NTT::b[m-1]=0; 135 NTT::Main(len); 136 for(j=0;j<len;j++) (f0[now][_ind[j%(m-1)]]+=NTT::c[j])%=p; 137 138 for(k=0;k<m;k++) 139 (f1[now][k*C(a[i+1],b[i+1])%m]+=f1[pst][k])%=p; 140 141 swap(now,pst); 142 } 143 for(i=0;i<m;i++) (ans[i]+=(f0[pst][i]+f1[pst][i])*type%p+p)%=p; 144 } 145 146 int main() 147 { 148 int i,j,x,cnt=0; 149 scanf("%d",&m); n=gi128(); l=gi128(); r=gi128(); l--; 150 mul[0]=mul[1]=_mul[0]=_mul[1]=1; 151 for(i=2;i<m;i++) mul[i]=mul[i-1]*i%m, _mul[i]=qpow(mul[i],m-2,m); 152 get_ind(); 153 solve(r,1); 154 solve(l,-1); 155 for(i=0;i<m;i++) printf("%lld ",ans[i]); 156 return 0; 157 }
以上是关于UOJ #86 mx的组合数 (数位DP+NTT+原根优化)的主要内容,如果未能解决你的问题,请参考以下文章
BZOJ 2425 [HAOI2010]计数:数位dp + 组合数