题解 P4714 【「数学」约数个数和】

Iowa_BattleShip

2018-06-21 13:45:15

Solution

这里给出矩阵快速幂的做法,个人认为矩乘比组合数好理解一些。 我们先考虑对于$p^m(p\text{为质数})$进行题目所描述的操作,我们可以先列出一个初始状态(第一行并不属于矩阵,只是表示第几列): $\qquad\qquad\qquad\qquad\begin{pmatrix}0&1&2&3&\cdots&m-1&m\\0&0&0&0&\cdots&0&1\end{pmatrix}$ 表示对于每一个$p^k(k\in[0,m])$,它的贡献是多少,起始只有一个数$p^m$,它的贡献即为$1$,其它均为$0$。 然后我们进行一次求约数的操作,因为$p^m$的约数为$p^0,p^1,p^2,\dots,p^m$,所以矩阵就变成这样: $\qquad\qquad\qquad\qquad\begin{pmatrix}0&1&2&3&\cdots&m-1&m\\1&1&1&1&\cdots&1&1\end{pmatrix}$ 再进行一次求约数操作,因为对于之前产生的约数$p^k$,它产生的约数为$p^0,p^1,p^2,\dots,p^k$,所以矩阵就变成这样: $\qquad\qquad\qquad\begin{pmatrix}0&1&2&3&\cdots&m-1&m\\m&m-1&m-2&m-3&\cdots&2&1\end{pmatrix}$ 于是我们就找到了递推的公式: 设矩阵为$f[i][j]$,$i$表示进行了几次求约数操作,$j$则表示$p^j$的贡献,初始状态为$f[0][0\to m]=1$,因为第$0$次即为进行过一次操作,对于每一层$i(i>0)$,有递推式$f[i][j]=\sum\limits_{k=j}^mf[i-1][k]$,最后递推至$K$层时,显然$\sum\limits_{i=0}^mf[K][i]$即为$p^m$这个数的贡献。 而根据算术基本定理,$N=\prod\limits_{i=1}^mp_i^{c_i}$,我们就可以对于每一个$p_i^{c_i}$用上述方法计算出它的贡献,而整个数$N$的总贡献即为$\prod\limits_{i=1}^m\sum\limits_{j=0}^mf[K][j]$。 然后我们考虑用矩阵快速幂去加速这个递推,因为原矩阵为$1\times (m+1)$大小,要使得乘完后仍为$1\times (m+1)$,显然我们需要一个$(m+1)\times (m+1)$的递推矩阵: $\qquad\qquad\qquad\quad\begin{pmatrix}&0&1&2&\cdots&m-1&m\\0&1&0&0&\cdots&0&0\\1&1&1&0&\cdots&0&0\\2&1&1&1&\cdots&0&0\\\vdots&\vdots&\vdots&\vdots&\ddots&\vdots&\vdots\\m-1&1&1&1&\cdots&1&0\\m&1&1&1&\cdots&1&1\end{pmatrix}$ 而因为$2^{60}=1,152,921,504,606,846,976>10^{18}$,所以$m<60$,跑的还是挺快的,于是剩下的时间就是将$N$分解质因数的时间了,如果使用朴素的$\sqrt{N}$算法显然会$T$,所以我们需要用$Miller-Rabin$算法和$Pollard-Rho$算法去分解,这里推荐个讲这两个算法的博客[$Miller-Rabin$算法](https://blog.csdn.net/sunshine_cfbsl/article/details/52425798)、[$Pollard-Rho$算法](https://blog.csdn.net/sunshine_cfbsl/article/details/52512706)。 在上述推导中其实我们发现这个矩阵只与这个次数$c_i$有关,这样我们可以稍微优化下,记录一个最大的$c_i$,然后用矩阵快速幂推出这个矩阵,再将其前缀和一下,这样就可以直接查找每个$c_i$下的贡献,同时在预处理时,我们也可以把相同的$c_i$合并,计算贡献时直接一个次方上去即可。 ```cpp #include<cstdio> #include<cstring> #include<cstdlib> #include<ctime> #include<algorithm> using namespace std; typedef long long ll; const int N=65; const int mod=998244353; int a[N],b[N],l,L,ma; //a记录不同的次方,b记录相同的次方个数,l为pr数组长度,L为b数组长度,ma为最大次方 ll pr[N<<4]; //pr为N分解出来的所以质因数 struct dd{//(m+1)*(m+1)的矩阵 ll T[N][N]; dd() { memset(T,0,sizeof(T)); } dd operator * (const dd& B)//自乘 { dd C; int i,j,k; for(i=0;i<=ma;i++) for(j=0;j<=ma;j++) for(k=0;k<=ma;k++) (C.T[i][j]+=T[i][k]*B.T[k][j]%mod)%=mod; return C; } }; struct jc{//1*(m+1)的矩阵 ll S[N]; jc() { memset(S,0,sizeof(S)); } jc operator * (const dd& B)//乘上(m+1)*(m+1)的矩阵 { jc C; int i,j; for(i=0;i<=ma;i++) for(j=0;j<=ma;j++) (C.S[i]+=S[j]*B.T[j][i]%mod)%=mod; return C; } }; jc an;//储存答案 dd tf;//递推矩阵 ll re()//快读 { ll x=0; char c=getchar(); for(;c<'0'||c>'9';c=getchar()); for(;c>='0'&&c<='9';c=getchar()) x=x*10+(c-'0'); return x; } void jc_ksm(ll y)//矩阵快速幂 { int i,j; memset(tf.T,0,sizeof(tf.T)); for(i=0;i<=ma;i++)//初始化为1 an.S[i]=1; for(i=0;i<=ma;i++)//初始化为一个形如三角形的1 for(j=0;j<=i;j++) tf.T[i][j]=1; for(;y;y>>=1,tf=tf*tf)//快速幂 if(y&1) an=an*tf; } ll n_ksm(ll x,int y)//一个普通的快速幂 { ll s=1; for(;y;y>>=1,x=x*x%mod) if(y&1) s=s*x%mod; return s; } void su()//对答案矩阵进行前缀和处理 { int i; for(i=ma;i;i--) (an.S[i-1]+=an.S[i])%=mod; } //以下为Miller和Porllard ll mu(ll x,ll y,ll M) { ll s=0; for(;y;y>>=1,x=(x+x)%M) if(y&1) s=(s+x)%M; return s; } ll ksm(ll x,ll y,ll M) { ll s=1; for(;y;y>>=1,x=mu(x,x,M)) if(y&1) s=mu(s,x,M); return s; } bool miller(ll x) { int i,j,t=0; ll y=x-1,X,Y; if(x==2) return true; for(;!(y&1);t++,y>>=1); for(j=1;j<=20;j++) { X=ksm(2+rand()%(x-2),y,x); for(i=1;i<=t;i++,X=Y) { Y=mu(X,X,x); if(Y==1&&X!=1&&X!=x-1) return false; } if(X!=1) return false; } return true; } ll gcd(ll x,ll y) { if(!y) return x; return gcd(y,x%y); } ll poll(ll x,ll c) { ll i,k=2,X=1+rand()%(x-1),Y=X,G; for(i=1;;i++) { X=(mu(X,X,x)+c)%x; G=gcd((Y-X+x)%x,x); if(G!=1&&G!=x) return G; if(X==Y) return x; if(i==k) { Y=X; k<<=1; } } } void sch(ll x,ll c) { ll k=c,p=x; if(x==1) return; if(miller(x)) { pr[++l]=x; return; } while(p>=x) p=poll(p,k--); sch(p,c); sch(x/p,c); } //以上为Miller和Porllard void ud(int k)//记录次方 { if(!b[k])//若没有出现 { a[++L]=k; b[k]=1; ma=ma>k?ma:k; } else//若出现过 b[k]++; } int main() { int i,k=0; ll n,m,s=1; n=re(); m=re(); sch(n,113);//分解质因数 sort(pr+1,pr+l+1);//排序 for(i=1;i<=l;i++)//记录 { if(i==1||pr[i]==pr[i-1]) k++; else { ud(k); k=1; } } if(k) ud(k); jc_ksm(m);//矩阵快速幂 su();//前缀和 for(i=1;i<=L;i++)//按上述进行累乘出答案 (s*=n_ksm(an.S[ma-a[i]],b[a[i]]))%=mod; printf("%lld",s); return 0; } ```