2025.6.10【ZR NOI模拟赛 T3】 过啥题 题解(Lucas 定理, 数位dp, 组合意义)
为什么70分这么唐考试没写啊。。。。
题意
T T T 组数据, 每组给定 n , m , k n, m, k n,m,k, 对于所有长度为 k k k 并且满足以下条件的 正整数 数列 { a i } \{a_i\} {ai}, { b i } \{b_i\} {bi}:
- ∑ i = 1 k a i = n \sum\limits_{i = 1}^{k}a_i = n i=1∑kai=n
- ∑ i = 1 k b i = m \sum\limits_{i = 1}^{k}b_i = m i=1∑kbi=m
计算 ∏ i = 1 k min ( a i , b i ) \prod\limits_{i = 1}^{k}\min(a_i,b_i) i=1∏kmin(ai,bi) 的和。对 10007 10007 10007 取模。
1 ≤ T ≤ 100 , 1 ≤ k ≤ n , m ≤ 10 18 1 \leq T \leq 100, 1 \leq k \leq n,m \leq 10^{18} 1≤T≤100,1≤k≤n,m≤1018
分析
注意到模数比较小, 并且 n , m n, m n,m 范围比较大,感觉起来就是数位 d p dp dp 题。
先来尝试给 ∏ i = 1 k min ( a i , b i ) \prod\limits_{i = 1}^{k}\min(a_i,b_i) i=1∏kmin(ai,bi) 编一个组合意义:发现它就等于 有多少长度为 k k k 的正整数序列 { x i } \{x_i\} {xi},满足 ∀ i ∈ [ 1 , k ] , x i ≤ a i 且 x i ≤ b i \forall i \in[1, k], x_i \leq a_i 且 x_i \leq b_i ∀i∈[1,k],xi≤ai且xi≤bi。
那么可以枚举 { x i } \{x_i\} {xi},计算一个 { x i } \{x_i\} {xi} 会被算几次。在 x i x_i xi 的基础上调整 a i , b i a_i, b_i ai,bi,设枚举的 x i x_i xi 的和为 S S S,那么答案就等于:
∑ S = k min ( n , m ) ( S − 1 k − 1 ) ( n − S + k − 1 k − 1 ) ( m − S + k − 1 k − 1 ) \sum\limits_{S = k}^{\min(n, m)}\binom{S - 1}{k - 1}\binom{n -S+k - 1}{k - 1}\binom{m - S+k - 1}{k - 1} S=k∑min(n,m)(k−1S−1)(k−1n−S+k−1)(k−1m−S+k−1)
将式子化的好看一些。令 k ′ = k − 1 , n ′ = n + k ′ − 1 , m ′ = m + k ′ − 1 k' = k - 1,n' = n + k'-1, m' = m + k' - 1 k′=k−1,n′=n+k′−1,m′=m+k′−1,
那么答案为:
∑ S = k ′ min ( n ′ , m ′ ) − k ′ ( S k ′ ) ( n ′ − S k ′ ) ( m ′ − S k ′ ) \sum\limits_{S = k'}^{\min(n', m') - k'}\binom{S}{k'}\binom{n' -S}{k'}\binom{m' - S}{k'} S=k′∑min(n′,m′)−k′(k′S)(k′n′−S)(k′m′−S)
注意到 S S S 如果不在 [ k ′ , min ( n ′ , m ′ ) − k ′ ] [k', \min(n', m') - k'] [k′,min(n′,m′)−k′] 之间那么这个式子的值肯定等于 0 0 0,因此可以认为对 S S S 的范围没有限制。
根据 卢卡斯定理,可以将组合数拆成 m o d mod mod 进制下每位求组合数然后相乘。我们不能枚举 S S S,考虑在 m o d mod mod 进制下依次考虑 S S S 的每一位。
此时 k ′ , n ′ , m ′ k', n',m' k′,n′,m′ 固定,先把它们在 m o d mod mod 进制下的每一位求出来,用 0 0 0 将最高位补到 max ( n ′ , m ′ ) \max(n', m') max(n′,m′) 的最高位。
那么 n ′ − S n' - S n′−S 就是每一位分别相减,由于是减法,可能会涉及退位,因此我们从高位到低位 d p dp dp。
发现由于 S S S 没有范围限制,那么状态只需要记录是否退位。设 d p x , 0 / 1 , 0 / 1 dp_{x, 0/1, 0/1} dpx,0/1,0/1 表示从高到低考虑到第 x x x 位, n ′ − S n' - S n′−S 是否向 x − 1 x - 1 x−1 位退位, m ′ − S m' - S m′−S 是否向 x − 1 x - 1 x−1 位退位的方案数。转移就是枚举第 x − 1 x - 1 x−1 位上 S S S 的数字,然后乘上这一位上组合数的贡献。
最后答案就是 d p 0 , 0 , 0 dp_{0, 0, 0} dp0,0,0。
复杂度 O ( T × m o d × log m o d max ( n , m ) ) O(T \times mod \times \log_{mod}\max(n, m)) O(T×mod×logmodmax(n,m))。
CODE:
// 首先需要根据组合意义推导出来一个简洁的式子。 然后发现模数比较小, 并且形式比较好
// 可以根据lucas定理拆成 mod 进制下数位dp
// 复杂度是 T * mod * log_{mod}max(n, m)
#include<bits/stdc++.h>
#define pb emplace_back
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
const int mod = 10007;
int fac[mod], inv[mod];
LL n, m, k;
int dp[6][2][2]; // dp[x][0/1][0/1] 表示从高到低考虑到第 x 位, 前面确定了 i 的若干位, n - i 前面往 x - 1 是否退位, m - i 往 x - 1 是否退位的方案数之和
inline int Pow(int x, int y) {int res = 1, k = x;while(y) {if(y & 1) res = res * k % mod;y >>= 1;k = k * k % mod;}return res;
}
inline int C(int n, int m) {if(n < m) return 0;return fac[n] * inv[m] % mod * inv[n - m] % mod;
}
inline int solve(LL n, LL m, LL k) {vector< int > N, M, K;while(n) N.pb(n % mod), n /= mod;while(m) M.pb(m % mod), m /= mod;while(k) K.pb(k % mod), k /= mod;int sz = max({N.size(), M.size(), K.size()});while(N.size() < sz) N.pb(0);while(M.size() < sz) M.pb(0);while(K.size() < sz) K.pb(0);memset(dp, 0, sizeof dp);dp[sz][0][0] = 1;for(int i = sz; i > 0; i -- ) // 刷表for(int a = 0; a <= 1; a ++ ) for(int b = 0; b <= 1; b ++ ) if(dp[i][a][b]) for(int c = 0; c <= 1; c ++ ) for(int d = 0; d <= 1; d ++ ) for(int x = 0; x < mod; x ++ ) {int u = N[i - 1] - x + a * mod - c, v = M[i - 1] - x + b * mod - d;if(u >= 0 && u < mod && v >= 0 && v <= mod) dp[i - 1][c][d] = (dp[i - 1][c][d] + dp[i][a][b] * C(x, K[i - 1]) % mod * C(u, K[i - 1]) % mod * C(v, K[i - 1]) % mod) % mod;}return dp[0][0][0];
}
int main() {fac[0] = 1; for(int i = 1; i < mod; i ++ ) fac[i] = fac[i - 1] * i % mod;inv[mod - 1] = Pow(fac[mod - 1], mod - 2); for(int i = mod - 2; i >= 0; i -- ) inv[i] = inv[i + 1] * (i + 1) % mod;int T; scanf("%d", &T);while(T -- ) {scanf("%lld%lld%lld", &n, &m, &k);k --; n = n + k - 1, m = m + k - 1;printf("%d\n", solve(n, m, k));}return 0;
}