一、题目
二、解法
0x01 朴素dp
需要把题目中的这n个数的和是p的倍数
转化成 这
n
n
n个数的和
%
p
\%p
%p等于
0
0
0,因为保证至少一个数是质数很难,我们可以用 全部-不含质数的方案数,设
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]为前
i
i
i个数的和
%
p
\%p
%p的值为
j
j
j的总方案数,转移如下:
d
p
[
i
]
[
j
]
=
∑
d
p
[
i
−
1
]
[
(
j
−
k
+
p
)
%
p
]
×
c
n
t
[
k
]
dp[i][j]=\sum dp[i-1][(j-k+p)\%p]\times cnt[k]
dp[i][j]=∑dp[i−1][(j−k+p)%p]×cnt[k]时间复杂度
O
(
n
p
2
)
O(np^2)
O(np2),可以拿到
30
30
30分的高分。
0x02 矩阵加速
看到
n
n
n这么大应该能想到矩阵加速把,我们可以构造出如下的矩阵:
用来乘的矩阵:
c
n
t
0
c
n
t
1
.
.
.
.
.
.
c
n
t
p
−
1
\begin{matrix}cnt_0\\cnt_1\\......\\cnt_{p-1}\end{matrix}
cnt0cnt1......cntp−1
快速幂矩阵:
c
n
t
0
c
n
t
p
−
1
c
n
t
p
−
2
.
.
.
.
.
.
c
n
t
1
c
n
t
0
c
n
t
p
−
1
.
.
.
.
.
.
.
.
.
.
.
.
.
\begin{matrix}cnt_0&cnt_{p-1}&cnt_{p-2}......\\cnt_1&cnt_0&cnt_{p-1}......\\.......\end{matrix}
cnt0cnt1.......cntp−1cnt0cntp−2......cntp−1......
其实构造方法就是让两个矩阵对应元素和一定,然后我们跑两遍快速幂。(下面还有一种方法)
#include <cstdio>
#include <cstring>
const int mod = 20170408;
const int MAXM = 2e7+5;
int read()
{
int num=0,flag=1;char c;
while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
while(c>='0'&&c<='9')num=(num<<3)+(num<<1)+(c^48),c=getchar();
return num*flag;
}
int n,m,p,tot,ans[2],cnt[105],pr[MAXM/10];
bool vis[MAXM];
struct Matrix
{
int n,m,a[105][105];
Matrix() {n=m=0;memset(a,0,sizeof a);}
Matrix operator * (const Matrix &x) const
{
Matrix r;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
for(int k=1;k<=x.m;k++)
r.a[i][k]=(r.a[i][k]+1ll*a[i][j]*x.a[j][k])%mod;
r.n=n;r.m=x.m;
return r;
}
}A,F;
Matrix qkpow(Matrix a,int b)
{
Matrix res;
res.n=res.m=p;
for(int i=1;i<=p;i++)
res.a[i][i]=1;
while(b>0)
{
if(b&1) res=res*a;
a=a*a;
b>>=1;
}
return res;
}
void fuck(int f)
{
A.n=A.m=p;
F.n=p;F.m=1;
for(int i=1;i<=p;i++)
for(int j=1;j<=p;j++)
A.a[i][j]=cnt[(i-j+p)%p];
for(int i=1;i<=p;i++)
F.a[i][1]=cnt[i-1];
ans[f]=(qkpow(A,n-1)*F).a[1][1];
}
signed main()
{
n=read();m=read();p=read();
for(int i=0;i<p;i++) cnt[i]=m/p;
for(int i=1;i<=m%p;i++)
cnt[i]++;
fuck(0);
for(int i=2;i<=m;i++)
{
if(!vis[i]) cnt[i%p]--,pr[++tot]=i;
for(int j=1;j<=tot && i*pr[j]<=m;j++)
{
vis[i*pr[j]]=1;
if(i%pr[j]==0) break;
}
}
fuck(1);
printf("%d\n",(ans[0]-ans[1]+mod)%mod);
}
0x03 对dp深入思考
发现本题选数的限制很小,我们可以改写
d
p
dp
dp方程,设
f
[
i
]
[
j
]
f[i][j]
f[i][j]为有
i
i
i个数
%
p
\%p
%p后的值为
j
j
j的方案数,转移:
f
[
i
]
[
j
]
=
∑
f
[
m
i
d
]
[
k
]
×
f
[
m
i
d
]
[
(
j
−
k
+
p
)
%
p
]
f[i][j]=\sum f[mid][k]\times f[mid][(j-k+p)\%p]
f[i][j]=∑f[mid][k]×f[mid][(j−k+p)%p]现在我们就只需要求
m
i
d
mid
mid的
d
p
dp
dp值了,然后不需要优化的时间复杂度是
O
(
log
n
p
2
)
O(\log np^2)
O(lognp2)。
这个式子可以用
FFT
\text{FFT}
FFT优化,有优化后的复杂度是
O
(
log
n
p
log
p
)
O(\log np\log p)
O(lognplogp)。
咕咕咕