【動態規劃20】bzoj4818[sdoi2017]序列計數(dp+矩陣快速冪)

題目描述

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

輸入輸出格式

一行三個數,n,m,p。
1<=n<=10^9,1<=m<=2×10^7,1<=p<=100
一行一個數,滿足Alice的要求的序列數量,答案對20170408取模。

題目顯然容斥原理,用所有方案-不含質數的方案爲所求。
首先,有一個非常菜的dp方程。
f[i][j]表示前i個數%p==j的序列個數
f[i][j]+=f[i1][(jk) %p](1<=k<=m)
那麼就是一個顯然的矩陣乘法。
首先f[i][]只與f[i-1][]相關,那麼我們可以將原來的i維消掉。
那麼之後的轉移就是將所有f[(jk) %p](1<=k<=m) 轉移到f[j]
那矩陣就很好構建了,從1到m枚舉k,矩陣的第j行的(j-k)%p列的值就加一,很好理解。(對於質數在枚舉過程中特殊判斷即可)
但是若是每一行都從1到m枚舉k,時間複雜度上是不允許的。
但是很顯然,我們可以意識到矩陣的第j+1行的(j+1-k)%p列,實際上就是j行的前一列(直接寫出來有點像廢話..),所以直接暴力從矩陣的上一行轉移,這樣子最後就是一個p*p的矩陣。
直接快速冪搞就完事了。

#include<bits/stdc++.h>
#define fer(i,j,n) for(int i=j;i<=n;i++)
#define far(i,j,n) for(int i=j;i>=n;i--)
#define ll long long
const int maxn=20000010;
const int INF=1e9+7;
const int mod=20170408;
using namespace std;
/*----------------------------------------------------------------------------*/
inline ll read()
{
    char ls;ll x=0,sng=1;
    for(;ls<'0'||ls>'9';ls=getchar())if(ls=='-')sng=-1;
    for(;ls>='0'&&ls<='9';ls=getchar())x=x*10+ls-'0';
    return x*sng;
}
/*----------------------------------------------------------------------------*/
int n,m,p,cnt;
int prime[maxn],f[110];
bool flag[maxn];
struct kaga
{
    ll v[110][110];
    kaga friend operator *(kaga a,kaga b)
    {
        kaga c;
        fer(i,0,p-1)
            fer(j,0,p-1)
            {
                c.v[i][j]=0;
                fer(k,0,p-1)
                c.v[i][j]+=a.v[i][k]*b.v[k][j]%mod;
            }
        return c;
    }
    kaga friend operator ^(kaga a,ll k)
    {
        kaga c;
        fer(i,0,p-1)
            fer(j,0,p-1)
                if(i==j)c.v[i][j]=1;
                else c.v[i][j]=0;
        for(;k;k>>=1,a=a*a)
            if(k&1)c=c*a;
        return c;
    }
    void friend print(kaga a)
    {
        fer(i,0,p-1)
        {
            fer(j,0,p-1)
                cout<<a.v[i][j]<<" ";
            cout<<endl; 
        }   
    }
}a;
void Prime(int n)
{
    memset(flag,0,sizeof(flag));
    flag[1]=1;
    cnt=0;
    fer(i,2,n)
    {
        if(!flag[i])prime[++cnt]=i;
        for(int j=1;j<=cnt&&i*prime[j]<=n;j++)
        {
            flag[i*prime[j]]=1;
            if(!(i%prime[j]))break; 
        }
    }
}
int solve1()
{
    fer(i,1,m)f[i%p]++;
    fer(i,1,m)a.v[(-i%p+p)%p][0]++;
    fer(i,1,p-1)
        fer(j,0,p-1)
            a.v[j][i]=a.v[(j-1+p)%p][i-1];
    a=a^(n-1);
    int ans=0;
    fer(i,0,p-1)ans=(ans+(ll)f[i]*a.v[i][0]%mod)%mod;
    return ans;
}
int solve2()
{
    memset(f,0,sizeof(f));
    fer(i,1,m)if(flag[i])f[i%p]++;
    memset(a.v,0,sizeof(a.v));
    fer(i,1,m)if(flag[i])a.v[(-i%p+p)%p][0]++;
    fer(i,1,p-1)
        fer(j,0,p-1)
            a.v[j][i]=a.v[(j-1+p)%p][i-1];
    a=a^(n-1);
    int ans=0;
    fer(i,0,p-1)ans=(ans+(ll)f[i]*a.v[i][0]%mod)%mod;
    return ans;
}
int main()
{
    n=read();m=read();p=read();
    Prime(m);
    cout<<(solve1()-solve2()+mod)%mod;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章