BZOJ 2142 - 礼物
Published on 2016-06-17描述
一年一度的圣诞节快要来到了。每年的圣诞节小 E 都会收到许多礼物,当然他也会送出许多礼物。不同的人物在小 E 心目中的重要性不同,在小 E 心中分量越重的人,收到的礼物会越多。小 E 从商店中购买了 件礼物,打算送给 个人,其中送给第 个人礼物数量为 。请你帮忙计算出送礼物的方案数(两个方案被认为是不同的,当且仅当存在某个人在这两种方案中收到的礼物不同)。由于方案数可能会很大,你只需要输出模 后的结果。
分析
如果 无解输出 Impossible
。
否则我们推下公式:
展开后裂项可得:
为了方便处理,如果 的话,我们补一个 即可当作 处理。
本题中 并不是质数,处理这种非质数取模的问题,一般采用中国剩余定理(Chinese remainder theorem,CRT)。
将 分解为 的形式,可以知道 两两互质,所以我们只要知道答案模 的值,即可通过中国剩余定理合并为模 的值。
所以我们只用考虑答案模 的值,这个数也可能不是质数,而且阶乘高达 级别,是根本无法直接算的,这需要我们进一步挖掘阶乘的性质。
我们先考虑 即 就是质数的情况。注意到,即使 是质数,这个式子仍然无法直接算!因为分母中可能存在 这个因子,而 对 是没有逆元的!
我们考虑将分子分母变为为 ,也就是将阶乘 分解为 的形式。
定理:如果 ,则 中素数 的指数不会小于 中素数 的指数。
证明:对于 的分解式子中,素数 的指数为:
而 的分解式子中,素数 的指数为:
由于是向下取整,所以 中 的个数不会小于 中 的个数,得证。这说明 一定是有意义的。
现在,我们需要对 快速分解,得到 ,其中 由于不含 这个因子,是可以模 的。
我们发现 的计算过程是以 为周期的,举例如下,当 时:
最后一步中的 可递归处理,所以我们预处理出 的阶乘,对于每个 即可 求解。
对于分母中的 ,不需任何处理,只需要将最后分解出的 中的 用扩展欧几里得求出逆元即可。
那么 怎么办?只需对原算法稍加修改即可。令 为除去 的倍数外 的阶乘。例如 ,那么 ,除去了 的倍数 、 和 。阶乘依然是以 为周期的。在算法内部,统计因子 的个数时,每次应该加 而不是 ,递归处理时也应该是递归到 。
详细参见代码。
代码
// Created by Sengxian on 6/17/16. // Copyright (c) 2016年 Sengxian. All rights reserved. // BZOJ 2142 阶乘模任意数 #include <bits/stdc++.h> using namespace std; typedef long long ll; inline int ReadInt() { static int n, ch; n = 0, ch = getchar(); while (!isdigit(ch)) ch = getchar(); while (isdigit(ch)) n = (n << 3) + (n << 1) + ch - '0', ch = getchar(); return n; } void extgcd(ll a, ll b, ll &x, ll &y) { if (b == 0) x = 1, y = 0; else { extgcd(b, a % b, y, x); y -= (a / b) * x; } } ll china(int n, int *a, int *m) { ll M = 1, w, x, y, ans = 0; for (int i = 0; i < n; ++i) M *= m[i]; for (int i = 0; i < n; ++i) { w = M / m[i]; extgcd(w, m[i], x, y); (ans += w * x * a[i]) %= M; } return (ans + M) % M; } inline ll mod_pow(ll a, int b, int modu) { ll res = 1; while (b) { if (b & 1) (res *= a) %= modu; (a *= a) %= modu; b >>= 1; } return res; } const int maxn = 5 + 3, maxs = 6 + 3, maxp = 100000 + 3; int n, p, m, w[maxn], a[maxs], mm[maxs]; int fact[maxp]; ll mod_fact(int n, const int &P, const int &p, ll &e) { if (n < p) return fact[n]; //注意不是 P 而是 p e += n / p; return mod_pow(fact[P - 1], n / P, P) * fact[n % P] % P * mod_fact(n / p, P, p, e) % P; } ll inv(ll a, ll p) { static ll x, y; extgcd(a, p, x, y); return (x % p + p) % p; } ll cal(int P, int p) { fact[0] = fact[1] = 1; for (int i = 2; i < P; ++i) fact[i] = (ll)fact[i - 1] * (i % p ? i : 1) % P; ll e1 = 0, e2 = 0; ll a = mod_fact(n, P, p, e1), b = 1; for (int i = 0; i < m; ++i) (b *= mod_fact(w[i], P, p, e2)) %= P; return a * inv(b, P) % P * mod_pow(p, e1 - e2, P) % P; } int main() { #ifndef ONLINE_JUDGE freopen("test.in", "r", stdin); #endif int sum = 0; p = ReadInt(), n = ReadInt(), m = ReadInt(); for (int i = 0; i < m; ++i) sum += w[i] = ReadInt(); if (sum > n) return puts("Impossible"), 0; else if (sum < n) w[m++] = n - sum; int cnt = 0; for (int i = 2; (ll)i * i <= p; ++i) if (p % i == 0) { int P = 1; while (p % i == 0) p /= i, P *= i; mm[cnt] = P, a[cnt++] = cal(P, i); } if (p > 1) mm[cnt] = p, a[cnt++] = cal(p, p); printf("%lld\n", china(cnt, a, mm)); return 0; }