P3702 [SDOI2017]序列计数
题目:
求 满足下列要求的序列个数:
1.由不超过m的正整数构成的
2.长度为n,而且这n个数的和是p的倍数。
3.n个数中,至少有一个数是质数。
不考虑排列
n
<
=
1
0
9
n<=10^9
n<=109 ,
m
<
=
2
×
1
0
7
m<=2\times 10^7
m<=2×107 ,
p
<
=
100
p<=100
p<=100
题解:
这题
O
(
n
m
)
O(n^m)
O(nm)的暴搜绝对超时,所以就要从p入手。
然后思考怎么是p的倍数,要最后结果是p的倍数,就表示n个数每个数mod p后的和是0
——摘自洛谷 昕远地自偏
\text{}
感觉思想跟单调队列优化多重背包有点像
因为要求质数数>=1,这个不好控制,转换成总方案数-非质数组成方案数
令
f
(
i
)
(
j
)
f(i)(j)
f(i)(j)表示长度为
i
i
i 的序列 且和%p为
j
j
j 的方案数
令
g
(
i
)
(
j
)
g(i)(j)
g(i)(j)表示长度为
i
i
i 的序列 且序列全部由非质数构成 且和%p为
j
j
j 的方案数
显然有
枚举每一个数
i
i
i ,
f
(
1
)
(
i
f(1)(i
f(1)(i %
p
)
+
+
p)++
p)++
枚举每一个非质数数
i
i
i ,
g
(
1
)
(
i
g(1)(i
g(1)(i %
p
)
+
+
p)++
p)++
\text{}
\text{}
当我们合并2个长度分别为
n
,
m
n,m
n,m 的序列时,显然有 (任意组合,乘法原理)
f
(
n
+
m
)
(
(
i
+
j
)
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ f (n+m) \ ((i+j)
f(n+m) ((i+j) %
p
)
=
f
(
n
)
(
i
)
×
f
(
m
)
(
j
)
p) = f(n)( i)\times f(m)(j)
p)=f(n)(i)×f(m)(j)
若不看第二维
f
′
(
n
+
m
)
=
f
′
(
n
)
×
f
′
(
m
)
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ f' (n+m) = f'(n)\times f'(m)
f′(n+m)=f′(n)×f′(m)
是不是很像 同底数幂的乘法、指数?
\text{ \ \ \ \ \\ \ \ \\ \\ \ \ \\ \\ \ \ \\ \\ \ \ \\ \\ \ \ \\ \\ \ \ \\ \\ \ \ \\ \\ \ \ \\ \\ \ \ \ \ }
a
i
+
j
=
a
i
a
j
a^{i+j}=a^i a^j
ai+j=aiaj
或者换个方式理解:
\text{ \ \ \ \ \\ \ \ \\ \\ \ \ \\ \\ \ \ \\ \ \ \\ \\ \ \ \\ \\ \ \\ \\ \ \ \\ \\ \ \ \\ \\ \ \ \ \ }
f
′
(
n
)
=
f
′
(
n
/
2
)
2
f'(n)=f'(n/2)^2
f′(n)=f′(n/2)2
可以用快速幂来优化,消掉一维
\text{}
\text{}
那么最后的问题就是,维护在快速幂下的单次更新
回到第二维:
f
(
i
)
f(i)
f(i)的含义更新:序列长度为
n
,
n
/
2
,
n
/
4
,
n
/
8......
n ,n/2 ,n/4, n/8 .... ..
n,n/2,n/4,n/8....... 且和余数为 i 的方案数
f
(
(
i
+
j
)
\ f ((i+j)
f((i+j) %
p
)
=
f
(
i
)
×
f
(
j
)
p) = f( i)\times f(j)
p)=f(i)×f(j)
由于
p
<
=
100
p<=100
p<=100 ,可以暴力
O
(
p
2
)
O(p^2)
O(p2) 乘起来
若是
p
p
p 比较大, 可以考虑
f
f
t
,
n
t
t
fft,ntt
fft,ntt 优化成
O
(
p
×
l
o
g
2
p
)
O(p\times log_{2}{p})
O(p×log2p)
#include<cstdio>
#define LL long long
const int N=110;
const LL mod=20170408;
int read(){
int x=0;char c=getchar();
while(c<48) c=getchar();
while(c>47) x=x*10+c-'0',c=getchar();
return x;
}
int pre[2000100],cnt=0,p;
bool v[20001000];
LL f[N],g[N],d[N],F[N],G[N];
void mul(LL *c,LL *a,LL *b)
{
for(int i=0;i<p;i++) d[i]=0;
for(int i=0;i<p;i++)
for(int j=0;j<p;j++)
d[(i+j)%p]=(d[(i+j)%p] + a[i]*b[j])%mod;
for(int i=0;i<p;i++) c[i]=d[i];
}
int main()
{
int n=read(),m=read();p=read();
f[1]=g[1]=1;//1%任何数为1,总情况f包括1,非质数g包括1
for(int i=2;i<=m;i++){
f[i%p]++;//,printf("f[%d]=%d ",i%p,f[i%p]);//全部构成
if(!v[i]) pre[++cnt]=i;
else g[i%p]++;//,printf("g=%d ",g[i%p]);//非质数构成
for(int j=1;j<=cnt,pre[j]*i<=m;j++){
v[i*pre[j]]=true;
if(i%pre[j]==0) break;
}
}//printf("%d ",cnt);
//F[0]=G[0]=1; <-这个是别人的初始化,不会证明,还是用下面这种吧
for(int i=0;i<p;i++) F[i]=f[i];
for(int i=0;i<p;i++) G[i]=g[i]; n--;
while(n){
if(n&1==1) mul(F,F,f),mul(G,G,g);
mul(f,f,f);mul(g,g,g);
n>>=1;
}printf("%lld",(F[0]-G[0]+mod)%mod);
return 0;
}