P3702 [SDOI2017]序列计数
题意
求长度为 \(n\) 的元素是不超过 \(m\) 的正整数的序列,其和是 \(p\) 的倍数并且元素中有至少一个质数的方案数。
Solution
暴力 OGF 好题!
考虑 \(p\) 比较小,容易想到在模 \(p\) 的剩余系下做。然后看到至少一个质数,容易想到正难则反。那问题转化成给定一个集合,然后求集合中元素组成的序列和在 \(p\) 的剩余系下是 \(0\) 的方案数。
然后直接掏出一个 OGF:\([x^n]f\) 表示集合中有多少个数在 \(p\) 的剩余系下是 \(n\)。那么 \(f^2\) 就表示长度为 \(2\) 的序列中和的生成函数。那么我们求 \(f^n\) 就得到长度为 \(n\) 的序列的和的剩余系的 OGF 了。
由于 \(f\) 大小不超过 \(p\),写一个剩余系下的乘法,然后套一个快速幂就可以了。复杂度 \(O(m+p^2\log n)\)。
不要开 long long
!!!
Code
using namespace std;
const int MAXN=2e7+10;
const int MOD=20170408;
int P;
struct Poly{int f[110];};
Poly mul(Poly a,Poly b){
Poly tmp;
memset(tmp.f,0,sizeof(tmp.f));
rep(i,0,P-1) rep(j,0,P-1)
(tmp.f[(i+j)%P]+=1ll*a.f[i]*b.f[j]%MOD)%=MOD;
return tmp;
}
int p[MAXN];
vector<int> pr;
Poly F,G,Rf,Rg;
void init(int m){
p[1]=1;
rep(i,2,m){
if(!p[i]) pr.pb(i);
for(int j=0;j<(int)pr.size()&&i*pr[j]<=m;j++){
p[i*pr[j]]=1;
if(i%pr[j]==0) break;
}
}rep(i,1,m){
F.f[i%P]++;
if(p[i]) G.f[i%P]++;
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int n,m;cin>>n>>m>>P;
init(m);
memset(Rf.f,0,sizeof(Rf.f));Rf.f[0]=1;
memset(Rg.f,0,sizeof(Rg.f));Rg.f[0]=1;
while(n){
if(n&1) Rf=mul(Rf,F),Rg=mul(Rg,G);
F=mul(F,F);G=mul(G,G);n>>=1;
}
cout<<((Rf.f[0]-Rg.f[0])%MOD+MOD)%MOD<<'\n';
return 0;
}