题目
题目背景
“我是你妈,学霸!” ——炎翼鸟
题目描述
何为约数?如果
a
∣
b
a\mid b
a∣b 那么
b
a
\frac{b}{a}
ab 是整数。这就是约数。由于
a
a
a 是分母,我们可以认为「
a
a
a 是
b
b
b 的母亲」。
现在,已知 2 , 3 , 5 , 7 , 11 , 47 2,3,5,7,11,47 2,3,5,7,11,47 为雄性,并且给出 k k k 个 [ 1 , 9 ] [1,9] [1,9] 之间的数字, 你需要找出,有多少个 n n n 位正整数满足每一位的数字都在给出的 k k k 个数字中,并且不让 2 , 3 , 5 , 7 , 11 , 47 2,3,5,7,11,47 2,3,5,7,11,47 成为母亲。
数据范围与提示
n
≤
1
0
9
n\le 10^9
n≤109 。显然
1
≤
k
≤
9
1\le k\le 9
1≤k≤9 。输出取模
9973
\tt 9973
9973 。
思路
显然不用管 2 , 5 2,5 2,5,因为这个只需要特殊处理最后一位。
令 P = 3 × 7 × 11 × 47 = 10857 P=3\times 7\times 11\times 47=10857 P=3×7×11×47=10857,我们只需要知道模 P P P 的余数。
直接数位 d p \tt dp dp 是 O ( n P ) \mathcal O(nP) O(nP) 的,显然对于这个 n n n 不太合适……我们来发掘一些性质。
一开始我还以为,这是一道数论题。想想我们已知的结论:模 3 3 3 只需要求每一位的和,模 7 7 7 是三位加、三位减,模 11 11 11 是奇数位减偶数位……
上面的都是小学奥数范畴。真正的原理在于
10
m
o
d
3
=
1
0
6
m
o
d
7
=
1
0
3
m
o
d
11
=
1
10\bmod 3=10^6\bmod 7=10^{3}\bmod 11=1
10mod3=106mod7=103mod11=1
毕竟是十进制数。类似的,我们很容易找到
1
0
φ
(
47
)
=
1
0
46
≡
1
(
m
o
d
47
)
10^{\varphi(47)}=10^{46}\equiv 1\pmod{47}
10φ(47)=1046≡1(mod47) 。令
δ
=
l
c
m
(
1
,
6
,
3
,
46
)
=
138
\delta={\rm lcm}(1,6,3,46)=138
δ=lcm(1,6,3,46)=138,我们会发现
1
0
δ
≡
1
(
m
o
d
3
,
7
,
11
,
47
)
10^{\delta}\equiv 1\pmod{3,7,11,47}
10δ≡1(mod3,7,11,47)
这提示我们,每 δ \delta δ 个数位分组,直接把每一组相加即可。相当于选出 ⌊ n δ ⌋ \lfloor\frac{n}{\delta}\rfloor ⌊δn⌋ 个 [ 0 , 1 0 δ ) [0,10^{\delta}) [0,10δ) 之间的数相加,求模 P P P 的每种余数的方案数。这就直接生成函数的快速幂了啊!
选择一个 [ 0 , 1 0 δ ) [0,10^{\delta}) [0,10δ) 之间的数字,这东西怎么求?使用 O ( δ P ) \mathcal O(\delta P) O(δP) 的大力数位 d p \tt dp dp 就好了嘛。如果 δ ∤ n \delta\nmid n δ∤n 会剩下 O ( δ ) \mathcal O(\delta) O(δ) 个数位,同理。
所以我们就做完了。时间复杂度 O ( δ P + P log P log n δ ) \mathcal O(\delta P+P\log P\log{n\over \delta}) O(δP+PlogPlogδn),基本上都乘了一个 k k k 。
当然,你可以适当的调大 δ \delta δ 来平衡复杂度……
代码
这是一个弱化版,可用数字是给定的。反正也不影响做法 😗
我用的
M
T
T
\tt MTT
MTT(即拆位
N
T
T
\tt NTT
NTT)。双模
N
T
T
\tt NTT
NTT 也可以。反正我不是很信赖
F
F
T
\tt FFT
FFT(我才不会告诉你,我已经把板子忘光了),然而它也能过。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
inline int readint(){
int a = 0; char c = getchar(), f = 1;
for(; c<'0'||c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c&&c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
const int Mod = 998244353;
inline int qkpow(long long b,int q){
int a = 1;
for(; q; q>>=1,b=b*b%Mod)
if(q&1) a = a*b%Mod;
return a;
}
const int LogMod = 30;
int g[2][LogMod], inv2[LogMod];
void prepare(){
int p = Mod-1, x = 0;
inv2[0] = 1, inv2[1] = (Mod+1)>>1;
for(; !(p&1); p>>=1,++x)
inv2[x+1] = 1ll*inv2[x]*inv2[1]%Mod;
g[1][x] = qkpow(3,p);
g[0][x] = qkpow(g[1][x],Mod-2);
for(; x; --x){
g[1][x-1] = 1ll*g[1][x]*g[1][x]%Mod;
g[0][x-1] = 1ll*g[0][x]*g[0][x]%Mod;
}
}
const int Loop = 10857;
const int MaxN = Loop<<2;
const int mod = 9973;
inline void add(int&x,const int&y){
((x += y) >= mod) ? (x -= mod) : 0;
}
int went[MaxN];
void NTT(int a[],int n,int opt){
for(int i=1; i<(1<<n); ++i)
if(i < went[i])
swap(a[i],a[went[i]]);
for(int w=1,x=1; x<=n; w<<=1,++x)
for(int *p=a; p!=a+(1<<n); p+=(w<<1))
for(int i=0,v=1; i<w; ++i){
int t = 1ll*v*p[i+w]%Mod;
p[i+w] = (p[i]+Mod-t)%Mod;
p[i] = (p[i]+t)%Mod;
v = 1ll*v*g[opt][x]%Mod;
}
if(!opt) rep(i,0,(1<<n)-1)
a[i] = 1ll*a[i]*inv2[n]%Mod;
}
int tmpa[MaxN], tmpb[MaxN];
void mul(int a[],int b[],int n){
for(int i=0; i<(1<<n); ++i){
tmpa[i] = a[i]/100, a[i] %= 100;
tmpb[i] = b[i]/100, b[i] %= 100;
went[i] = (went[i>>1]>>1)|((i&1)<<n>>1);
}
NTT(tmpa,n,1), NTT(a,n,1);
NTT(tmpb,n,1), NTT(b,n,1);
for(int i=0; i<(1<<n); ++i){
int two = 1ll*tmpa[i]*tmpb[i]%Mod;
int one = (1ll*tmpa[i]*b[i]+1ll*a[i]*tmpb[i])%Mod;
int zero = 1ll*a[i]*b[i]%Mod;
tmpa[i] = two, a[i] = one, tmpb[i] = zero;
}
NTT(tmpa,n,0), NTT(a,n,0), NTT(tmpb,n,0);
for(int i=0; i<(1<<n); ++i)
a[i] = (10000ll*(tmpa[i]%mod)+100ll*(a[i]%mod)+tmpb[i])%mod;
for(int i=Loop; i<(1<<n); ++i)
a[i%Loop] = (a[i%Loop]+a[i])%mod, a[i] = 0;
}
int dp[2][Loop];
const int number_table[] = {1,2,3,5,7};
void getDP(int n){
for(int i=1,fr=0; i<=n; ++i,fr^=1){
memset(dp[i&1],0,Loop<<2);
rep(j,0,Loop-1) rep(k,0,4)
add(dp[i&1][(10*j+number_table[k])%Loop],dp[fr][j]);
}
}
int b[MaxN], tmp[MaxN], res[MaxN];
const int Rest = 138, LogN = 15;
int work(int n){
memset(dp[0],0,Loop<<2);
dp[0][0] = 1; getDP(Rest);
memcpy(b,dp[Rest&1],Loop<<2);
memset(res,0,MaxN<<2), res[0] = 1;
int big = (n-1)/Rest, small = n-1-big*Rest;
for(; big; big>>=1){
if(big&1){
memcpy(tmp,b,MaxN<<2);
mul(res,tmp,LogN);
}
memcpy(tmp,b,MaxN<<2);
mul(b,tmp,LogN);
}
memcpy(dp[0],res,Loop<<2);
getDP(small); // not include last digit
memset(res,0,MaxN<<2);
rep(j,0,Loop-1) rep(k,0,4)
if(k != 1 && k != 3) // not multiple of 2/5
add(res[(10*j+number_table[k])%Loop],dp[small&1][j]);
int ans = 0;
rep(i,0,Loop-1) if(i%3 && i%7 && i%11 && i%47)
add(ans,res[i]); // can be seleted
return ans;
}
int main(){
prepare();
int n = readint();
printf("%d\n",work(n));
return 0;
}
后记
上面这个做法是我考场上乱搞的……题解有一些更神奇的做法。
令
f
(
i
,
j
)
f(i,j)
f(i,j) 为
[
0
,
1
0
i
)
[0,10^i)
[0,10i) 之间的数字模
P
P
P 为
j
j
j 的个数。显然有转移
f
(
2
n
,
i
)
=
∑
1
0
n
⋅
j
+
k
≡
i
(
m
o
d
P
)
f
(
n
,
j
)
⋅
f
(
n
,
k
)
f(2n,i)=\sum_{10^n\cdot j+k\equiv i\pmod{P}}f(n,j)\cdot f(n,k)
f(2n,i)=10n⋅j+k≡i(modP)∑f(n,j)⋅f(n,k)
这东西其实也是卷积,只是不那么明显。先把 f ( n , j ) f(n,j) f(n,j) 全部放到 1 0 n ⋅ j 10^n\cdot j 10n⋅j 上面去就行了!即 g ( x ) = ∑ 1 0 n ⋅ j ≡ x ( m o d P ) f ( n , j ) g(x)=\sum_{10^{n}\cdot j\equiv x\pmod{P}}f(n,j) g(x)=∑10n⋅j≡x(modP)f(n,j),那么转移就是 f ( n ) f(n) f(n) 和 g g g 的循环卷积。复杂度 O ( P log P log n ) \mathcal O(P\log P\log n) O(PlogPlogn) 。
H
a
n
d
I
n
D
e
v
i
l
\sf HandInDevil
HandInDevil 说,如果
δ
\delta
δ 位用这个做法(而不用
O
(
P
δ
)
\mathcal O(P\delta)
O(Pδ) 硬算),就可以得到优秀做法!不过我没兴趣打。