@bzoj - 2142@ 礼物


@description@

一年一度的圣诞节快要来到了。每年的圣诞节小E都会收到许多礼物,当然他也会送出许多礼物。不同的人物在小E心目中的重要性不同,在小E心中分量越重的人,收到的礼物会越多。
小E从商店中购买了n件礼物,打算送给m个人,其中送给第i个人礼物数量为wi。
请你帮忙计算出送礼物的方案数(两个方案被认为是不同的,当且仅当存在某个人在这两种方案中收到的礼物不同)。
由于方案数可能会很大,你只需要输出模P后的结果。

Input
输入的第一行包含一个正整数P,表示模;
第二行包含两个整整数n和m,分别表示小E从商店购买的礼物数和接受礼物的人数;
以下m行每行仅包含一个正整数wi,表示小E要送给第i个人的礼物数量。

Output
若不存在可行方案,则输出“Impossible”,否则输出一个整数,表示模P后的方案数。

Sample Input
100
4 2
1
2

Sample Output
12

【样例说明】
下面是对样例1的说明。
以“/”分割,“/”前后分别表示送给第一个人和第二个人的礼物编号。12种方案详情如下:
1/23 1/24 1/34
2/13 2/14 2/34
3/12 3/14 3/24
4/12 4/13 4/23

【数据规模和约定】
设P=p1^c1 * p2^c2 * p3^c3 * … *pt ^ ct,pi为质数。
对于100%的数据,1≤n≤109,1≤m≤5,1≤pici≤10^5。

@solution@

令 sum = 所有的 wi 之和,则答案 ans 的表达式如下:

\[\frac{n!}{w_1!*w_2!*...*w_m!*(n-sum)!} \]

当 sum > n 时无解。问题转换为求上式对 P 取模的值。
此时我们需要扩展 lucas 定理(因为 P 不一定是质数所以要扩展的)。
(mark 一下yhn学长的博客)。

首先可以将 P 唯一分解一下,得到 P = p1^c1 * p2^c2 * p3^c3 * … * pt^ct。然后分开求再中国剩余定理合并(中国剩余定理自行百度可以学)。
那么实际上我们只需要考虑模数为质数的幂的情况,不妨设模数为 p^c。
我们通过扩展 lucas 将阶乘 n! 拆写成 A * p^B 的形式,其中 A 与 p 互质。这样的形式下做阶乘之间的除法,只需要 A 部分直接逆元,B 部分直接减即可。

怎么才能将阶乘 n! 拆写成 A * p^B 的形式呢?
先将 \(n! = 1*2*...*n\) 写下,然后将其中 p 的倍数共同提出一个 p 因子来,得到 \(n! = (n/p)!*p^{n/p}*1*2*...*(p-1)*(p+1)*...\)
注意到此时整个式子可以分成三部分:\((n/p)!\) 这一部分递归求解;\(p^{n/p}\) 这一部分融入上面的 B;\(1*2*...*(p-1)*(p+1)*...\) 这一部分融入上面的 A。
但是第三部分还是不好求。继续观察发现第三部分是有循环节的(因为是在模 p^c 的意义下的),求出循环节的个数、循环节内的乘积、剩余部分的乘积即可。

或许有人认为 lucas 定理只适用于组合数,但通过上文我们可以发现,扩展意义下的 lucas 定理可以用于模意义下阶乘之间的乘除。
也许改天可以用这个性质出个什么题。

@accepted code@

#include<cstdio>
#include<vector>
#include<iostream>
using namespace std;
const int MAXN = 100000;
typedef pair<int, int> pii;
int pow_mod(int b, int p, int m) {
	int ret = 1;
	while( p ) {
		if( p & 1 ) ret = 1LL*ret*b%m;
		b = 1LL*b*b%m;
		p >>= 1;
	}
	return ret;
}
vector<int>vec;
int fct[MAXN + 5];
pii lucas(int x, int b, int m) {
	if( x == 0 ) return make_pair(1, 0);
	pii tmp = lucas(x/b, b, m);
	return make_pair(1LL*tmp.first*pow_mod(fct[m-1], x/m, m)%m*fct[x%m]%m, tmp.second+x/b);
}
int P, n, m;
int main() {
	scanf("%d%d%d", &P, &n, &m);
	int tmp = P, sum = 0, ans = 0;
	for(int i=1;i<=m;i++) {
		int x; scanf("%d", &x);
		vec.push_back(x), sum += x;
	}
	if( sum > n ) {
		puts("Impossible");
		return 0;
	}
	vec.push_back(n-sum);
	for(int i=2;i<=MAXN&&i<=tmp;i++)
		if( tmp % i == 0 ) {
			int m = 1;
			while( tmp % i == 0 )
				tmp /= i, m *= i;
			fct[0] = 1;
			for(int j=1;j<m;j++) {
				fct[j] = fct[j-1];
				if( j % i ) fct[j] = 1LL*fct[j]*j%m;
			}
			pii res = lucas(n, i, m);
			for(int j=0;j<vec.size();j++) {
				pii tmp2 = lucas(vec[j], i, m);
				res.first = 1LL*res.first*pow_mod(tmp2.first, m - m/i - 1, m)%m;
				res.second -= tmp2.second;
			}
			int del = 1LL*res.first*pow_mod(i, res.second, m)%m;
			ans = (ans + 1LL*(P/m)*pow_mod(P/m, m - m/i - 1, m)%P*del%P)%P;
		}
	printf("%d\n", ans);
}

@details@

题目中真的没有 P 的范围。。。找了很久都没找到。。。

看到网上的题解大多 P 用的是 long long,还以为我开 int 过不了。
结果其实过得了的。。。

posted @ 2019-07-07 21:18  Tiw_Air_OAO  阅读(308)  评论(0编辑  收藏  举报