[ACNOI2021]数位DP板题

280 篇文章 1 订阅

题目

题意概要
对于所有 n ∈ [ 1 , r n ] n\in[1,r_n] n[1,rn],求出数位和为 n 3 n^3 n3 的正整数中,第 n 4 n^4 n4 大的一个。输出对 p p p 取模。

数据范围与提示
r n ⩽ 1 0 3 r_n\leqslant 10^3 rn103 2 ⩽ p ⩽ 1 0 9 + 9 2\leqslant p\leqslant 10^9+9 2p109+9,但是不保证 p p p 为质数。

思路

我以为这个题完全不可做。因为 n 3 n^3 n3 根本存不下,从何数位 d p \tt dp dp 呢?

可是旧神 F i r e W i n g B i r d \sf FireWingBird FireWingBird 就可以做到——它只要运用独有技「随切」,就会看出端倪。

譬如我们数位 d p \tt dp dp 的思路,可以先枚举位数。显然至少要 k = ⌈ n 3 9 ⌉ k=\lceil\frac{n^3}{9}\rceil k=9n3 位,才能凑出第一个数。那么 k k k 位的合法拆分有多少个呢?显然有一个下界是 ( k 9 k − n 3 ) {k\choose 9k-n^3} (9kn3k),即所有只由 8 , 9 8,9 8,9 构成的数。那么,当 n ⩾ 9 n\geqslant 9 n9 k ⩾ n 2 k\geqslant n^2 kn2 时,只要 ( 9 k − n 3 ) ⩾ 2 (9k-n^3)\geqslant 2 (9kn3)2 就基本上能够达到 n 4 n^4 n4 的要求了!

这东西有什么用?其实上面的式子告诉我们, k k k 位只由 8 , 9 8,9 8,9 构成的数是 上界。而 k k k 本身的定义就是, k k k 位数(几乎全是 8 , 9 8,9 8,9 等)才能凑出 n 3 n^3 n3,是一个 下界。难道说——这就是一个确界吗?也就是说,答案很可能是只由 8 , 9 8,9 8,9 构成的 k k k 位数么?

倒也不完全是。至少方向是对的。再怎么样,答案的数位多数都是 9 9 9 。——似乎有点反常,反正我是感受不到这个结论的,直到旧神 F i r e W i n g B i r d \sf FireWingBird FireWingBird 开口。

给一点形式化的证明:当 ( k 9 k − n 3 ) ⩾ n 4 {k\choose 9k-n^3}\geqslant n^4 (9kn3k)n4 时,可以确定答案是 k k k 位数。设第一位为 x x x,则后面要凑出 n 3 − x > 9 ( k − 1 ) − x n^3-x>9(k-1)-x n3x>9(k1)x,也就是说,后面最多有 ( x − 1 ) (x-1) (x1) 个数位非 9 9 9

类似地,当 ( k 9 k − n 3 ) < n 4 ⩽ ( k + 1 9 ( k + 1 ) − n 3 ) {k\choose 9k-n^3}<n^4\leqslant{k+1\choose 9(k+1)-n^3} (9kn3k)<n4(9(k+1)n3k+1) 时,答案是 ( k + 1 ) (k+1) (k+1) 位数。显然此时 9 k − n 3 ≈ 1 9k-n^3\approx 1 9kn31,所以后面要凑出 n 3 − x ≈ 9 k − 1 − x n^3-x\approx 9k-1-x n3x9k1x,那么后面大抵最多只有 ( x + 1 ) (x+1) (x+1) 个数位非 9 9 9

这里出了点问题,为了证明结论,我不得不修正一下它……所以这一步可能不是很自然,是知道结论之后为了证明结论而临时打的补丁……

考虑上面那种情况, x = 8 x=8 x=8 时,后面已经至少有了 ( k 9 k + 8 − n 3 ) {k\choose 9k+8-n^3} (9k+8n3k) 种方案,哪怕 n 3 = 9 k n^3=9k n3=9k,也是大局已定!故而即使有 ( x + 1 ) (x+1) (x+1) 个数位非 9 9 9,也是 1 ⩽ x ⩽ 8 1\leqslant x\leqslant 8 1x8 的情况,所以最多还是 9 9 9 个数位非 9 9 9

上面皆旨在说明:当 n n n 略偏大时,答案中最多 9 9 9 个数位(除去首位)不是 9 9 9 。看上去就是很强有力的结论呢!于是我们考虑……考虑啥呢?数位 d p \tt dp dp 吗?数位好像并不少啊……

注意这个 9 9 9 。它恰好是数字 9 9 9,是十进制下的最大数位。我们如果 任取 9 9 9 个数位(可以重复选择) 1 1 1,那就会得到一个合法的拆分!并且这也就是所有方案!目标肯定是选取最大的数位减 1 1 1,可以从高到低二分每一个选择。

大坑:首位的选择不能放在二分内,因为首位未确定时,可以选超过 10 10 10 个数位非 9 9 9,就不满足条件了。

对于一个 n n n,时间复杂度 O ( D 2 log ⁡ n ) \mathcal O(D^2\log n) O(D2logn),即 D D D 次二分,每次二分检查时用 O ( D ) \mathcal O(D) O(D) 暴力计算组合数,其中 D = 10 D=10 D=10 为基数。做 n 0 n_0 n0 次也完全没问题!

代码

一个有趣的事情:只有 n = 1 n=1 n=1 n = 2 n=2 n=2 需要特判!与 n ⩾ 10 n\geqslant 10 n10 的粗略估计相差甚远。

不过,加入剪枝后, n ⩽ 10 n\leqslant 10 n10 的搜索速度也非常快。因为我们知道就是一堆 8 , 9 8,9 8,9 嘛 😂

#include <cstdio>
#include <iostream>
#include <vector>
#include <cstring>
#include <algorithm>
#include <cctype>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long llong;
inline int readint(){
	int a = 0, c = getchar(), f = 1;
	for(; !isdigit(c); c=getchar())
		if(c == '-') f = -f;
	for(; isdigit(c); c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
void writeint(llong x){
	if(x > 9) writeint(x/10);
	putchar(char((x%10)^48));
}

int qkpow(llong b,int q,int MOD){
	llong a = 1; b %= MOD;
	for(; q; q>>=1,b=b*b%MOD)
		if(q&1) a = a*b%MOD;
	return static_cast<int>(a);
}

/// @return if C( @p n , @p m ) > @p want
bool check(int n,int m,const llong &want){
	llong now = 1;
	if(m > (n>>1)) m = int(n-m);
	for(int i=1; i<=m; ++i)
		if(now <= want*i/(n+1-i))
			now = now*(n+1-i)/i;
		else return true;
	return false;
}

llong getC(int n,int m){
	llong res = 1;
	if(m > (n>>1)) m = int(n-m);
	for(int i=1; i<=m; ++i)
		res = res*(n+1-i)/i;
	return res;
}

/**
 * if there're @p m to choose OTHER THAN THIS ONE,
 * which digit(1-indexed) can be chosen
 * @note @p want is modified meanwhile
 */
int solve(int l,int r,int m,llong &want){
	if(check(r+m-1,r-1,want-1)) return r;
	llong all = getC(r+m,m+1);
	for(int x=(l+r+1)>>1; l!=r; x=(l+r+1)>>1)
		if(all-getC(x+m-1,m+1) >= want)
			l = x; // at least x-th digit
		else r = x-1; // cannot achieve that
	want -= (all-getC(l+m,m+1));
	return l; // the result
}

int main(){
	int n0 = readint(), p = readint();
	if(n0 == 1) return puts("1"), 0;
	printf("1 %d",161%p); // at least 2
	for(int n=3; n<=n0; ++n){
		int want = n*n*n; ///< needed digit-sum
		llong rnk = llong(n)*want; ///< needed rank of answer
		int k = (want+8)/9; // ceil(n/9)
		// k-digit number: k variables add up to (9*k-want)
		if(!check((9*k-want)+k-1,k-1,rnk-1)) // not fit in k-digit
			rnk -= getC((9*k-want)+k-1,k-1), ++ k;
		const int HIGHBIT = qkpow(10,k-1,p);
		int ans = HIGHBIT-1; // (k-1) digits of '9'
		int lst = k-1; ///< how many digits remains
		for(int head=1; true; ++head){
			int left = 9*(k-1)-(want-head);
			if(check(lst+left-1,lst-1,rnk-1)){
				ans = int((ans+llong(head)*HIGHBIT)%p);
				want -= head; break;
			}
			rnk -= getC(lst+left-1,lst-1);
		}
		for(int cnt=9*k-9-want; cnt; --cnt){
			lst = solve(1,lst,cnt-1,rnk);
			ans = (ans+p-qkpow(10,lst-1,p))%p;
		}
		printf(" %d",ans);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值