1. 程式人生 > >BZOJ_4818_[Sdoi2017]序列計數_矩陣乘法

BZOJ_4818_[Sdoi2017]序列計數_矩陣乘法

reg 想要 發現 ali ont true const 矩陣乘法 scrip

BZOJ_4818_[Sdoi2017]序列計數_矩陣乘法

Description

Alice想要得到一個長度為n的序列,序列中的數都是不超過m的正整數,而且這n個數的和是p的倍數。Alice還希望 ,這n個數中,至少有一個數是質數。Alice想知道,有多少個序列滿足她的要求。

Input

一行三個數,n,m,p。 1<=n<=10^9,1<=m<=2×10^7,1<=p<=100

Output

一行一個數,滿足Alice的要求的序列數量,答案對20170408取模。

Sample Input

3 5 3

Sample Output

33
求至少有一個質數的方案可以用總方案減去不含質數的方案。 先把1~m的質數篩出來,觀察p特別小,考慮每個數%p的值對答案的貢獻。 設F[i][j]表示從%p=i到%p=j的方案數,這個矩陣乘1次相當於向序列裏多塞了個數,於是這道題變成了矩陣乘法。 然後發現f[i][j]=f[i+1][j+1],因此只需要對每個1~m中的i,f[0][i%p]++即可,剩下的可以通過平移得到。 代碼:
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
typedef long long ll;
ll mod=20170408;
int n,m,p,prime[7000050],cnt;
bool vis[20000050];
struct Mat {
    ll v[105][105];
    Mat() {memset(v,0,sizeof(v));}
    Mat operator*(const Mat a) const {
        Mat ans;
        int i,j,k;
        for(i=1;i<=p;i++)
            for(j=1;j<=p;j++)
                for(k=1;k<=p;k++)
                    (ans.v[i][j]+=v[i][k]*a.v[k][j])%=mod;
        return ans;
    }
}A,B;
Mat pow(Mat x,int y) {
    Mat I;
    int i;
    for(i=1;i<=p;i++) I.v[i][i]=1;
    while(y) {
        if(y&1) I=I*x;
        x=x*x;
        y>>=1;
    }
    return I;
}
void init() {
    register int i,j;
    vis[1]=1;
    for(i=2;i<=m;i++) {
        if(!vis[i]) {
            prime[++cnt]=i;
        }
        for(j=1;j<=cnt&&i*prime[j]<=m;j++) {
            vis[i*prime[j]]=1;
            if(i%prime[j]==0) break;
        }
    }
}
int main() {
    int i,j;
    scanf("%d%d%d",&n,&m,&p);
    init();
    for(i=1;i<=m;i++) {
        A.v[p][(i-1)%p+1]++;
        if(vis[i]) B.v[p][(i-1)%p+1]++;
    }
    for(i=p-1;i;i--) {
        for(j=1;j<=p;j++) {
            A.v[i][j]=A.v[i+1][j%p+1];
            B.v[i][j]=B.v[i+1][j%p+1];
        }
    }
    printf("%lld\n",(pow(A,n).v[p][p]-pow(B,n).v[p][p]+mod)%mod);
}

BZOJ_4818_[Sdoi2017]序列計數_矩陣乘法