CODECHEF - No Minimum No Maximum
题目链接:https://www.codechef.com/problems/NMNMX
题意:给一个n个数的序列,n个数两两之间各不相同,然后指定一个长度k,选出其中所有的长度为k的子序列,然后每个子序列中除了最大值和最小值以外的元素都乘起来得到一个乘积,再把所有子序列的乘积再乘起来。求这个值模1e9+7。
题解:某个数要产生贡献,首先要被包含在某个子序列中,意味着这个元素必选,剩下的n-1个中选k-1个,这些选法中,假如这个数作为最小值或者作为最大值时要减掉,意味着要减掉从比它小的i-1个中选k-1个,以及比它大的n-i个中选k-1个这两种不合法的选法,易知这两种是不交叉的,不需要容斥。
计算这个组合数之后,这个组合数是要作为指数使用的,需要使用欧拉定理来降幂,所以组合数模的值是phi(mod),质数的模是其-1,就是1e9+6。这个值是不能够线性预处理出来的,至少知道有两种方法,一种是按组合数的定义算:
\(C_n^m=\frac{n!}{m!(n-m)!}\) ,这个结果一定是个整数,所以可以分别求出分子和分母的各个阶乘的质因数分解式拿去作差(用指数作差的方式避免除法,也就避免了乘法逆元),然后再进行一次快速幂(加法和乘法对任意模的模运算都是可以进行的)。预处理的复杂度是 \(O(np)\) ,p是质数的数量,单次求解的复杂度是 \(O(p)\) (然后带上一个很小的log),其实不预处理也可以,阶乘的质因数分解,就像我做的那个银牌题一样处理就可以。
const ll MOD = 1e9 + 7;
const ll PHIMOD = 1e9 + 6;
int p[705], ptop;
int pm[5005], pid[5005];
int fac[5005][705];
void InitCase() {
int n = 5000;
ptop = 0;
pm[1] = 1;
for(int i = 2; i <= n; ++i) {
if(!pm[i]) {
p[++ptop] = i;
pm[i] = i;
pid[i] = ptop;
for(int j = i; j <= n; j += i)
pm[j] = i;
}
}
memset(fac, 0, sizeof(fac));
for(int i = 2; i <= n; ++i) {
for(int j = 1; j <= ptop; ++j)
fac[i][j] = fac[i - 1][j];
int x = i;
while(x > 1) {
fac[i][pid[pm[x]]] += 1;
x /= pm[x];
}
}
}
ll qpow(ll x, ll n, ll MOD) {
ll res = 1;
while(n) {
if(n & 1)
res = res * x % MOD;
x = x * x % MOD;
n >>= 1;
}
return res;
}
ll C(int n, int m) {
if(n < m)
return 0;
ll res = 1;
for(int j = 1; j <= ptop; ++j) {
int cnt = fac[n][j] - fac[m][j] - fac[n - m][j];
res = res * qpow(p[j], cnt, PHIMOD);
if(res >= PHIMOD)
res %= PHIMOD;
}
return res;
}
int a[5005];
void TestCase() {
int n, k;
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
sort(a + 1, a + 1 + n);
ll res = 1;
for(int i = 1; i <= n; ++i) {
ll cnt = (C(n - 1, k - 1) - C(i - 1, k - 1) - C(n - i, k - 1)) % PHIMOD;
if(cnt < 0)
cnt += PHIMOD;
cnt += PHIMOD;
res = res * qpow(a[i], cnt, MOD);
if(res >= MOD)
res %= MOD;
}
printf("%lld\n", res);
}
另一种方法是利用 \(C_n^m=C_{n-1}^{m-1}+C_{n-1}^{m}\) 来计算,整个预处理出来。