P1025 · OFFICIAL SOLUTION

P1025 万象启星环 官方题解

Gioush OJ · P1025 万象启星环

万象启星环

【题目简述】

求长度为 nn 的有序序列 (x1,x2,,xn)(x_1,x_2,\ldots,x_n) 的数量,满足 1xim1\le x_i\le mxi\sum x_ipp 的倍数,且至少有一个 xix_i 为质数。答案对 998,244,353998{,}244{,}353 取模。

【数据点 1∼41\sim41∼4】

【直接 DP】

直接统计“至少一个质数”不方便,因此改为“所有序列数”减去“不含质数的序列数”。令 fi,r,0/1f_{i,r,0/1} 表示前 ii 个数已确定、和模 pprr 的方案数,最后一维区分可选全集与只选非质数。

【转移】

对每个 j[1,m]j\in[1,m] 枚举加入的数,转移到 (r+j)modp(r+j)\bmod p。复杂度为 O(nmp)\mathcal{O}(nmp)

【数据点 5∼85\sim85∼8】

【重复转移】

滚动数组后,每一层都执行同一个线性变换。令列向量 xi\bm{x}_i 记录各余数的方案数,若 A\bm{A} 表示一次转移,则 xn=Anx0\bm{x}_n=\bm{A}^n\bm{x}_0

【矩阵】

Count0,r={x1xm, xr(modp)},\operatorname{Count}_{0,r}=\left|\left\{x\mid 1\le x\le m,\ x\equiv r\pmod p\right\}\right|, Count1,r={x1xm, xr(modp), x 不是质数}.\operatorname{Count}_{1,r}=\left|\left\{x\mid 1\le x\le m,\ x\equiv r\pmod p,\ x\text{ 不是质数}\right\}\right|.

转移矩阵第 j,kj,k 项为 Count(jk+p)modp\operatorname{Count}_{(j-k+p)\bmod p}。分别对全集与非质数集合快速幂,答案是两者余数 00 的方案数之差。

【正解】

【计算计数】

Count0,r\operatorname{Count}_{0,r} 可以用带余除法直接求出;再用线性筛找出 [1,m][1,m] 中所有质数,从对应余数类扣除即可得到 Count1,r\operatorname{Count}_{1,r}

【解法】

构造两张 p×pp\times p 循环矩阵,分别快速幂 nn 次。初始向量只有余数 00 的分量为 11。两次转移后取余数 00 分量相减。

【复杂度分析】

线性筛为 O(m)\mathcal{O}(m),矩阵快速幂为 O(p3logn)\mathcal{O}(p^3\log n),总复杂度为 O(m+p3logn)\mathcal{O}(m+p^3\log n)

【参考代码】

#include <cstdio>#include <cstring>#include <algorithm>#include <iostream>using namespace std;const int Mod=998244353;inline int Add(int a,int b){return a+b>=Mod?a+b-Mod:a+b;}inline int Mul(int a,int b){return 1ll*a*b%Mod;}inline int Del(int a,int b){return a-b<0?a-b+Mod:a-b;}int T,n,m,p;int Prime[2000100],cnt=0,Count[2][110];bool Tag[20000010];void Tackle() {    Count[0][1%p]++;Count[1][1%p]++;    for (int i=2;i<=m;i++) {        Count[0][i%p]++;        if (!Tag[i]) {            Prime[++cnt]=i;        }        else Count[1][i%p]++;        for (int j=1;j<=cnt;j++) {            if (1ll*i*Prime[j]>m) {break;}            Tag[i*Prime[j]]=true;            if (i%Prime[j]==0) {break;}        }    }    return;}struct Matrix {    int Num[110][110];    void Init() {        memset(Num,0,sizeof(Num));    }}I,Ans1,f,g,Ans2;Matrix operator*(const Matrix &A,const Matrix &B) {    Matrix C;    for(int i=1;i<=p;i++) {        for(int j=1;j<=p;j++) {            C.Num[i][j]=0;            for(int k=1;k<=p;k++) {                C.Num[i][j]=Add(C.Num[i][j],Mul(A.Num[i][k],B.Num[k][j]));            }        }    }    return C;}void Print(Matrix A) {    for(int i=1;i<=p;i++) {        for(int j=1;j<=p;j++) {            cout<<A.Num[i][j]<<" ";        }        puts("");    }    return;}Matrix operator^(Matrix A,int b) {    Matrix C;    C=I;    while(b) {        if (b&1) C=C*A;        A=A*A;        b>>=1;    }    return C;}Matrix operator-(const Matrix &A,const Matrix &B) {    Matrix C;    C.Init();    for(int i=1;i<=p;i++) {        for(int j=1;j<=p;j++) {            C.Num[i][j]=Del(A.Num[i][j],B.Num[i][j]);        }    }    return C;}void Solve() {    scanf("%d%d%d",&n,&m,&p);    cnt=0;    memset(Count,0,sizeof(Count));    memset(Tag,0,(m+1)*sizeof(Tag[0]));    I.Init();    Ans1.Init();Ans2.Init();    for (int i=1;i<=p;i++) I.Num[i][i]=1;    Tackle();    f.Init();g.Init();    Ans1.Num[p][1]=1;Ans2.Num[p][1]=1;    for (int i=1;i<=p;i++) {        for (int j=1;j<=p;j++) {            f.Num[i][j]=Count[0][(j-i+p)%p];        }    }    for (int i=1;i<=p;i++) {        for (int j=1;j<=p;j++) {            g.Num[i][j]=Count[1][(j-i+p)%p];        }    }    Matrix Ans=(f^n)*Ans1-(g^n)*Ans2;    printf("%d\n",Ans.Num[p][1]);}int main() {    scanf("%d",&T);    while(T--) Solve();    return 0;}