【[SDOI2017]序列计数】

感觉自己的复杂度感人

大概是\(O(p*\pi(m)+p^3logn)\)

还是能过去的

我们看到这么大的数据范围还是应该先想一想暴力怎么写

显然我们可以直接暴力\(dp\)

\(dp[i][j]\)表示已经选择了\(i\)数,其中所有数的和\(mod\ p\)\(j\)的方案数

显然方程是

\[f[i][j]=\sum_{k=1}^mdp[i-1][((j-k)\%p+p)\%p]\]

初始的状态是\(dp[0][0]=1\),最终的答案是\(dp[n][0]\)

至于还有一个至少有一个素数的限制条件,我们可以先不管这个条件直接算一遍,之后再保证\(k\)不为素数再算一遍,两个一减就是答案了

这样暴力转移的复杂度是\(O(nmp)\)的,于是我们要考虑优化

这个转移相当的固定,于是可以矩乘优化

我们发现因为\(p\)非常的小,于是那个膜\(p\)意义下的转移会有很多重复的位置被转移过去,于是我们如果可以预处理出这样一个数组\(tot[j][k]\)

表示\(dp[i-1][k]\)会向\(dp[i][k]\)专一多少次,也就是\(dp[i][k]+=dp[i-1][j]*tot[j][k]\)

于是就有这样一个矩阵会被构造出来

tu

(\(p=3\)的情况)

于是就可以转移了,至于\(tot[j][k]\)怎么求,这个就是很简单了

在没有素数的情况下把所有素数对应的转移减一遍就好了

代码

#include<iostream>
#include<cstring>
#include<bitset>
#include<cstdio>
#define re register
#define maxn 20000005
#define LL long long
const LL mod=20170408;
std::bitset<maxn> f;
int prime[1500000];
LL ans[101][101],a[101][101];
int m,p;
LL n;
inline void did_a()
{
    LL mid[101][101];
    for(re int i=1;i<=p;i++)
        for(re int j=1;j<=p;j++)
            mid[i][j]=a[i][j],a[i][j]=0;
    for(re int i=1;i<=p;i++)
        for(re int j=1;j<=p;j++)
            for(re int k=1;k<=p;k++)
                a[i][j]=(a[i][j]+(mid[i][k]*mid[k][j])%mod)%mod;
}
inline void did_ans()
{
    LL mid[101][101];
    for(re int i=1;i<=p;i++)
        for(re int j=1;j<=p;j++)
            mid[i][j]=ans[i][j],ans[i][j]=0;
    for(re int i=1;i<=p;i++)
        for(re int j=1;j<=p;j++)    
            for(re int k=1;k<=p;k++)
                ans[i][j]=(ans[i][j]+(mid[i][k]*a[k][j])%mod)%mod;
}
inline void Rebuild()
{
    memset(ans,0,sizeof(ans));
    memset(a,0,sizeof(a));
    for(re int i=1;i<=p;i++)
        ans[i][i]=1;
    int t=m/p;
    for(re int i=1;i<=p;i++)
        for(re int j=1;j<=p;j++)
            a[i][j]=t;
    int tot=m%p;
    for(re int i=1;i<=p;i++)
    {
        int cnt=tot,x=i-1;
        if(!x) x=p;
        while(cnt)
        {
            a[i][x]++,cnt--;
            x--;
            if(!x) x=p;
        }
    }
}
inline void out()
{
    for(re int i=1;i<=p;i++)
        {
            for(re int j=1;j<=p;j++)
            printf("%d ",a[i][j]);
            putchar(10);
        }
}
inline void quick(LL b)
{
    while(b)
    {
        if(b&1ll) did_ans();
        b>>=1ll;
        did_a();
    }
}
int main()
{
    scanf("%lld%d%d",&n,&m,&p);
    f[1]=1;
    for(re int i=2;i<=m;i++)
    {
        if(!f[i]) prime[++prime[0]]=i;
        for(re int j=1;j<=prime[0]&&prime[j]*i<=m;j++)
        {
            f[prime[j]*i]=i;
            if(i%prime[j]==0) break;
        }
    }
    Rebuild();
    quick(n);
    LL num=ans[1][1];
    Rebuild();
    for(re int i=1;i<=p;i++)
    {
        for(re int j=1;j<=prime[0];j++)
        {
            a[i][((i-1-prime[j])%p+p)%p+1]--;
            if(a[i][(i-prime[j]+p)%p+1]<0) a[i][(i-prime[j]+p)%p+1]=mod-1;
        }
    }
    quick(n);
    std::cout<<(num-ans[1][1]+mod)%mod;
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/asuldb/p/10205779.html