[ACNOI2021]不要男妈妈

220 篇文章 2 订阅
4 篇文章 0 订阅

题目

题目背景
“我是你妈,学霸!” ——炎翼鸟

题目描述
何为约数?如果 a ∣ b a\mid b ab 那么 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 n109 。显然 1 ≤ k ≤ 9 1\le k\le 9 1k9 。输出取模 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)=10461(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)=10nj+ki(modP)f(n,j)f(n,k)

这东西其实也是卷积,只是不那么明显。先把 f ( n , j ) f(n,j) f(n,j) 全部放到 1 0 n ⋅ j 10^n\cdot j 10nj 上面去就行了!即 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)=10njx(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δ) 硬算),就可以得到优秀做法!不过我没兴趣打

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值