洛谷 P2183 [国家集训队]礼物)(扩展卢卡斯定理)

[国家集训队]礼物

题目背景

一年一度的圣诞节快要来到了。每年的圣诞节小 E 都会收到许多礼物,当然他也会送出许多礼物。不同的人物在小 E 心目中的重要性不同,在小 E 心中分量越重的人,收到的礼物会越多。

题目描述

小 E 从商店中购买了 n n n 件礼物,打算送给 m m m 个人,其中送给第 i i i 个人礼物数量为 w i w_i wi。请你帮忙计算出送礼物的方案数(两个方案被认为是不同的,当且仅当存在某个人在这两种方案中收到的礼物不同)。由于方案数可能会很大,你只需要输出模 P P P 后的结果。

输入格式

输入的第一行包含一个整数 P P P,表示模数。
第二行包含两个整数 n n n m m m,分别表示小 E 从商店购买的礼物数和接受礼物的人数。
3 3 3 到第 ( m + 2 ) (m + 2) (m+2) 行,每行一个整数,第 ( i + 2 ) (i + 2) (i+2) 行的整数 w i w_i wi 表示送给第 i i i 个人的礼物数量。

输出格式

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

样例 #1

样例输入 #1

100
4 2
1
2

样例输出 #1

12

样例 #2

样例输入 #2

100
2 2
1
2

样例输出 #2

Impossible

提示

样例 1 解释

/ 分割,/ 前后分别表示送给第一个人和第二个人的礼物编号。 12 12 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 = ∏ i = 1 t p i c i P= \prod_{i=1}^t p_i^{c_i} P=i=1tpici p i p_i pi 为质数。

对于 15 % 15\% 15% 的数据, n ≤ 15 n\leq 15 n15 m ≤ 5 m\leq 5 m5 p i c i ≤ 1 0 5 p_i^{c_i}\leq 10^5 pici105

在剩下的 85 % 85\% 85% 数据中,约有 60 % 60\% 60% 的数据满足 t ≤ 2 t\leq 2 t2 c i = 1 c_i=1 ci=1 p i ≤ 1 0 5 p_i\leq 10^5 pi105,约有 30 % 30\% 30% 的数据满足 p i ≤ 200 p_i\leq 200 pi200

对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 1 0 9 1\leq n\leq 10^9 1n109 1 ≤ m ≤ 5 1\leq m\leq 5 1m5 1 ≤ p i c i ≤ 1 0 5 1\leq p_i^{c_i}\leq 10^5 1pici105 1 ≤ w i ≤ P ≤ 1 0 9 1\leq w_i \leq P\leq 10^9 1wiP109

1、题目求若干个组合数对 p(不一定是素数)求模
2、有几个组合数,就调用几次 扩展卢卡斯定理 模板
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MaxN = 1000010;
ll nn, mm, pp;

ll exgcd(ll a, ll b, ll& x, ll& y)
{
	if(0 == b)
	{
		x = 1, y = 0;
		return 0;
	}
	ll d, x1, y1;
	d = exgcd(b, a % b, x1, y1);
	x = y1, y = x1 - a / b * y1;
	return d;
}

//计算 a^b mod p
ll quick_pow(ll a, ll b, ll p)
{
	ll c = 1;
	while(b)
	{
		if(b & 1)
			c = (c * a) % p;
		a = (a * a) % p;
		b >>= 1;
	}
	return c;
}

// 计算逆元 ax = 1(mod p), 算出最小非负整数 x 
ll inverse(ll a, ll p)	// gcd(a, p) = 1, 可以用好 exgcd 计算逆元x
{
	ll x, y;
	exgcd(a, p, x, y);
	return (x % p + p) % p;
}

// 递归 计算 n! mod p^k 的值
ll fac(ll n, ll  p, ll pk)
{
	if(!n)
		return 1;
	ll ans = 1;
	for(ll i = 1; i < pk; ++i)
	{
		if(i % p)
			ans = (ans * i) % pk;
	}
	ans = quick_pow(ans, n / pk, pk); 
	for(ll i = 1; i <= n % pk; ++i)		// 余下一部分,全部算上
	{
		if(i % p)
			ans = (ans * i) % pk;
	}
	return ans * fac(n / p, p, pk) % pk;
}

// 计算 c[m][n]  mod p^k 
ll combi(ll n, ll m, ll p, ll pk)
{
	if(n < m)
		return 0;
	ll  f1 = fac(n, p, pk), f2 = fac(m, p, pk), f3 = fac(n - m, p, pk);
	ll t1 = n, t2 = m, t3 = n - m, cnt = 0;
	for(; t1; t1 /= p)	
		cnt += t1 / p;
	for(; t2; t2 /= p)	
		cnt -= t2 / p;
	for(; t3; t3 /= p)	
		cnt -= t3 / p;
	return ((f1 * inverse(f2, pk) % pk) * inverse(f3, pk) % pk * quick_pow(p, cnt, pk)) % pk;
}


ll r[300], m[300];

ll crt(ll n)
{
	ll M = 1, ans = 0;
	for(ll i = 1; i <= n; ++i)
		M *= m[i];
	for(ll i = 1; i <= n; ++i)
	{
		ll c = M / m[i], x, y;
		exgcd(c, m[i], x, y);
		ans = (ans + r[i] * c * x % M) % M;
	}
	return (ans % M + M) % M;
}

ll exlucas(ll A, ll B, ll pp)
{
	memset(m, 0, sizeof m);
	memset(r, 0, sizeof r);
	ll tmp = pp;
	int cnt = 0;
	m[cnt] = 1;
	for(ll i = 2; i * i <= pp; ++i)
	{
		if(tmp % i == 0)
		{
			m[++cnt] = 1;
			while(tmp % i == 0)
				tmp /= i, m[cnt] *= i;
			r[cnt] = combi(A, B, i, m[cnt]);
		}
	}
	if(tmp > 1)
		m[++cnt] = tmp, r[cnt] = combi(A, B, tmp, m[cnt]);
	return crt(cnt);
}

int main()
{
	ll person[10], sum = 0;
	scanf("%lld%lld%lld", &pp, &nn, &mm);
	for(ll i = 1; i <= mm; ++i)
	{
		scanf("%lld", &person[i]);
		sum += person[i];
	}
	if(nn < sum)	// 不够分
	{
		printf("Impossible\n");
		return 0;
	}

	ll mod = pp;
	ll result = 1;
	for(ll i = 1; i <= mm; ++i)
	{
		result = result * exlucas(nn, person[i], mod) % mod;
		nn -= person[i];
	}
	printf("%lld\n", result);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值