1. 题面梗概
中译中 :给定 n n n ,求将 n n n 分解为若干个 互不相同 且 大于等于 2 的整数乘积的方案数。
n ≤ 10 12 , T ≤ 5 n \le 10^{12}, T \le 5 n ≤ 1 0 12 , T ≤ 5 ,结果模 998244353 998244353 998244353 。
688 = 2 × 4 × 86 = 2 × 8 × 43 = 2 × 344 = 4 × 172 = 8 × 86 = 16 × 43 688 = 2 \times 4 \times 86 = 2 \times 8 \times 43 = 2 \times 344 = 4 \times 172 = 8 \times 86 = 16 \times 43 688 = 2 × 4 × 86 = 2 × 8 × 43 = 2 × 344 = 4 × 172 = 8 × 86 = 16 × 43
2. 逻辑推导
由于 n ≤ 10 12 n \leq 10^{12} n ≤ 1 0 12 ,显然线性的 O ( n ) O(n) O ( n ) 做法也被卡了,我们得从 O ( n ) O( \sqrt{ n }) O ( n ) 的规模入手。
虽然 n n n 的因数个数 d ( n ) d(n) d ( n ) 却不大( 10 12 10^{12} 1 0 12 这个规模,d ( n ) d(n) d ( n ) 最大只有 6720 6720 6720 )。
既然是个计数类问题,我们不妨用 DP 来计数:
2.1 状态
如果求的是 "和为 n n n ",我们可以直接套 0/1 背包,本题要求乘法,不过也类似。
定义状态: d p [ i ] [ j ] dp[i][j] d p [ i ] [ j ] 表示使用前 j j j 个约数(即 f a c [ 2 … j ] fac[2 \dots j] f a c [ 2 … j ] ,跳过 1),使得乘积恰好等于第 i i i 个约数 f a c [ i ] fac[i] f a c [ i ] 的方案数。
对于每一个状态 d p [ i ] [ j ] dp[i][j] d p [ i ] [ j ] ,我们考虑第 j j j 个约数 f = f a c [ j ] f = fac[j] f = f a c [ j ] 的选取情况:
不选 f a c [ j ] fac[j] f a c [ j ] :方案数继承自前 j − 1 j-1 j − 1 个约数的结果。
d p [ i ] [ j ] = d p [ i ] [ j − 1 ] dp[i][j] = dp[i][j-1]
d p [ i ] [ j ] = d p [ i ] [ j − 1 ]
选 f a c [ j ] fac[j] f a c [ j ] (前提是 f a c [ i ] fac[i] f a c [ i ] 能被 f f f 整除): 我们需要凑出剩余的乘积 f a c [ i ] / f fac[i] / f f a c [ i ] / f ,且只能使用前 j − 1 j-1 j − 1 个约数(以保证互不相同)。
d p [ i ] [ j ] = ( d p [ i ] [ j − 1 ] + d p [ i d ( f a c [ i ] / f ) ] [ j − 1 ] ) ( m o d m o d ) dp[i][j] = (dp[i][j-1] + dp[id(fac[i]/f)][j-1]) \pmod{mod}
d p [ i ] [ j ] = ( d p [ i ] [ j − 1 ] + d p [ i d ( f a c [ i ] / f )] [ j − 1 ]) ( mod m o d )
2.2 映射技巧
由于 f a c [ i ] / f fac[i]/f f a c [ i ] / f 是一个具体的数值,我们需要 O ( 1 ) O(1) O ( 1 ) 找到它在 fac 数组中的下标。
利用 n / x n/x n / x 的分布特性,通过 pos1(处理 ≤ n \le \sqrt{n} ≤ n )和 pos2(处理 > n > \sqrt{n} > n )双数组映射来优化,防止数组开太大。
3. 代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 #include <bits/stdc++.h> using namespace std;using ll = long long ;const int maxm = 6720 + 5 ;const int maxn = 1e6 + 5 ;const int mod = 998244353 ;int dp[maxm][maxm]; ll fac[maxm];int pos1[maxn], pos2[maxn], cnt; ll n, sq;int get ( ll x ) { if ( x <= sq ) return pos1[x]; else return pos2[n / x]; }void solve ( ) { cin >> n; sq = (ll)sqrt ( n ); cnt = 0 ; for ( ll i = 1 ; i * i <= n; ++ i ) { if ( n % i == 0 ) { fac[++ cnt] = i; if ( i * i != n ) fac[++ cnt] = n / i; } } sort ( fac + 1 , fac + cnt + 1 ); for ( int i = 1 ; i <= cnt; ++ i ) { if ( fac[i] <= sq ) pos1[ fac[i] ] = i; else pos2[ n / fac[i] ] = i; for ( int j = 1 ; j <= cnt; ++ j ) dp[i][j] = 0 ; } for ( ll j = 0 ; j <= cnt; ++ j ) dp[1 ][j] = 1 ; for ( ll j = 2 ; j <= cnt; ++ j ) { ll f = fac[j]; for ( ll i = 2 ; i <= cnt; ++ i ) { dp[i][j] = dp[i][j - 1 ]; if ( fac[i] % f == 0 ) dp[i][j] = ( dp[i][j] + dp[ get ( fac[i] / f ) ][j-1 ] ) % mod; } } ll ans = dp[cnt][cnt] - 1 ; cout << ( ans + mod ) % mod << '\n' ; }int main ( ) { ios::sync_with_stdio (0 ); cin.tie (0 ); int _t = 1 ; cin >> _t ; while ( _t -- ) solve ( ); return 0 ; }
4. 复盘