题目
题意概要
对于所有
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
rn⩽103 且
2
⩽
p
⩽
1
0
9
+
9
2\leqslant p\leqslant 10^9+9
2⩽p⩽109+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} (9k−n3k),即所有只由 8 , 9 8,9 8,9 构成的数。那么,当 n ⩾ 9 n\geqslant 9 n⩾9 即 k ⩾ n 2 k\geqslant n^2 k⩾n2 时,只要 ( 9 k − n 3 ) ⩾ 2 (9k-n^3)\geqslant 2 (9k−n3)⩾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 (9k−n3k)⩾n4 时,可以确定答案是 k k k 位数。设第一位为 x x x,则后面要凑出 n 3 − x > 9 ( k − 1 ) − x n^3-x>9(k-1)-x n3−x>9(k−1)−x,也就是说,后面最多有 ( x − 1 ) (x-1) (x−1) 个数位非 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} (9k−n3k)<n4⩽(9(k+1)−n3k+1) 时,答案是 ( k + 1 ) (k+1) (k+1) 位数。显然此时 9 k − n 3 ≈ 1 9k-n^3\approx 1 9k−n3≈1,所以后面要凑出 n 3 − x ≈ 9 k − 1 − x n^3-x\approx 9k-1-x n3−x≈9k−1−x,那么后面大抵最多只有 ( 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+8−n3k) 种方案,哪怕 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 1⩽x⩽8 的情况,所以最多还是 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 n⩾10 的粗略估计相差甚远。
不过,加入剪枝后, n ⩽ 10 n\leqslant 10 n⩽10 的搜索速度也非常快。因为我们知道就是一堆 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;
}