BZOJ 4818 [Sdoi2017]序列计数
2018.04.09
2018.04.09
求长度为$n$,每个数都为不超过$m$的正整数,和为$p$的倍数,至少有一个数为质数的序列个数。
正难则反,想到可以用所有和为$p$的倍数的序列个数减去没有质数的和为$p$的倍数的序列个数。
设$f_{i,j}$为前$i$个数和$\mod p = j$的总方案数,用$cnt_i$表示$1 \sim m$中$\mod p = i$的数的个数,于是有$f_{i,j} = \sum\limits_{k=0}^{p-1} f_{i-1,k} + cnt_{(j-k) \text{mod} p}$.这个东西发现可以矩乘优化($n \le 10^9$)所以搞一下就好啦。
同理用$cnt_i$再表示一次$1 \sim m$中$\mod p = i$的合数个数,once again,减掉就可以了。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
#define mo 20170408
int n,m,p,cnt[101];
ll ans;
struct matrix
{
ll v[101][101];
ll *operator[](int a){return v[a];}
matrix(){memset(v,0,sizeof v);}
matrix operator*(matrix x)
{
int i,j,k;
matrix ret;
for(k=0;k<p;++k)
for(i=0;i<p;++i)
for(j=0;j<p;++j)
ret[i][j]=(ret[i][j]+v[i][k]*x[k][j])%mo;
return ret;
}
matrix operator^(int x)
{
matrix ret,tmp=*this;
for(int i=0;i<p;++i) ret.v[i][i]=1;
while(x)
{
if(x&1) ret=ret*tmp;
tmp=tmp*tmp,x>>=1;
}
return ret;
}
}res,lim;
int prm[20000010],tot;
bool v[20000010];
void init()
{
int i,j;
for(i=2;i<=m;++i)
{
if(!v[i]) prm[++tot]=i;
for(j=1;j<=tot&&i*prm[j]<=m;++j)
{
v[i*prm[j]]=true;
if(i%prm[j]==0) break;
}
}
for(i=1;i<=tot;++i) cnt[prm[i]%p]--;
}
int main()
{
scanf("%d%d%d",&n,&m,&p);
int i,j,t;
for(i=0,t=m/p;i<p;++i) cnt[i]=t;
for(i=1,t=m%p;i<=t;++i) cnt[i]++;
for(i=0;i<p;++i) for(j=0;j<p;++j) lim[i][j]=cnt[(j-i+p)%p];
memset(res.v,0,sizeof res.v),res[0][0]=1,res=res*(lim^n),ans=res[0][0];
init();
for(i=0;i<p;++i) for(j=0;j<p;++j) lim[i][j]=cnt[(j-i+p)%p];
memset(res.v,0,sizeof res.v),res[0][0]=1,res=res*(lim^n),ans=(ans-res[0][0]+mo)%mo;
printf("%lld\n",ans);
return 0;
}