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
100
4 2
1
2
样例输出1
12
样例输入2
100
2 2
1
2
样例输出2
Impossible

说明/提示

样例 1 解释

/ 分割,/ 前后分别表示送给第一个人和第二个人的礼物编号。 12 12 12 种方案详情如下:

1/2 3   1/2 4   1/3 4
2/1 3   2/1 4   2/3 4
3/1 2   3/1 4   3/2 4
4/1 2   4/1 3   4/2 3

数据规模与约定

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

Code

#include <bits/stdc++.h>
#define int long long
using namespace std;
int w[10005];
int p, n, m, s, k = 1;

void exgcd(int a, int b, int &x, int &y)
{
	if (b)
	{
		exgcd(b, a % b, y, x);
		y -= a / b * x;
	}
	else
		x = 1, y = 0;
}

int inv(int a, int p)
{
	int x, y;
	exgcd(a, p, x, y);
	return (x + p) % p;
}

int qmi(int a, int b, int p)
{
	int res = 1;
	while (b)
	{
		if (b & 1)
			res = res * a % p;
		a = a * a % p;
		b >>= 1;
	}
	return res;
}

int f(int a, int p, int k)
{
	if (!a)
		return 1;
	int i, u = 1, v = 1;
	for (i = 1; i < k; ++i)
		if (i % p)
			u = i * u % k;
	for (i = a / k * k; i <= a; ++i)
		if (i % p)
			v = i % k * v % k;
	return f(a / p, p, k) * qmi(u, a / k, k) % k * v % k;
}

int g(int a, int p)
{
	if (a < p)
		return 0;
	return g(a / p, p) + a / p;
}

int h(int a, int b, int p, int k)
{
	return f(a, p, k) * inv(f(b, p, k), k) % k * inv(f(a - b, p, k), k) % k * qmi(p, g(a, p) - g(b, p) - g(a - b, p), k) % k;
}

int exlucas(int a, int b, int p)
{
	int i, j, k, l, s = 0;
	for (i = 2, j = p; i * i <= j; ++i)
	{
		if (j % i)
			continue;
		for (k = i, j /= i; !(j % i); k *= i)
			j /= i;
		l = p / k;
		s = (s + l * h(a, b, i, k) % p * inv(l, k)) % p;
	}
	if (j > 1)
	{
		l = p / j;
		s = (s + l * h(a, b, j, j) % p * inv(l, j)) % p;
	}
	return s;
}

signed main()
{
	cin >> p >> n >> m;
	for (int i = 0; i < m; ++i)
	{
		cin >> w[i];
		s += w[i];
	}
	if (s > n)
	{
		puts("Impossible");
		return 0;
	}
	for (int i = 0; i < m; ++i)
	{
		k = k * exlucas(n, w[i], p) % p;
		n -= w[i];
	}
	cout << k;
	return 0;
}

广告

绿树公司 - 官方网站:https://wangping-lvshu.github.io/LvshuNew/

绿树智能 - 官方网站:https://wangping-lvshu.github.io/LvshuZhineng/

(现在使用,人人均可获得300元大奖)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lvshu · 绿树

非常感谢您的搭讪

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值