洛谷 P5505 [JSOI2011] 分特产

[JSOI2011] 分特产

题目描述

JYY 带队参加了若干场 ACM/ICPC \text{ACM/ICPC} ACM/ICPC 比赛,带回了许多土特产,要分给实验室的同学们。

JYY 想知道,把这些特产分给 n n n 个同学,一共有多少种不同的分法?当然,JYY 不希望任何一个同学因为没有拿到特产而感到失落,所以每个同学都必须至少分得一个特产。

例如,JYY 带来了 2 2 2 袋麻花和 1 1 1 袋包子,分给 A A A B B B 两位同学,那么共有 4 4 4 种不同的
分配方法:

A A A:麻花, B B B:麻花、包子

A A A:麻花、麻花, B B B:包子

A A A:包子, B B B:麻花、麻花

A A A:麻花、包子, B B B:麻花

输入格式

输入数据:

第一行是同学的数量 n n n 和特产的数量 m m m

第二行包含 m m m 个整数,表示每一种特产的数量。

n , m n, m n,m 不超过 1000 1000 1000 ,每一种特产的数量不超过 1000 1000 1000

输出格式

输出一行,不同分配方案的总数。

由于输出结果可能非常巨大,你只需要输出最终结果
  m o d     1 0 9 + 7 \bmod\ {10^9+7} mod 109+7 的数值就可以了。

样例 #1

样例输入 #1

5 4
1 3 3 5

样例输出 #1

384835

Solve:

每个同学都要分到特产,直接求并不是很好求,考虑容斥。
设有 n n n 种性质,其中第 i i i 种性质 a i a_i ai 表示:至少有 i i i 个同学拿不到特产,同时设 N ( a ) N(a) N(a) 表示满足性质a的方案数,定义 N ( 1 ) N(1) N(1) 为全集的数量.
则所求为 N ( ∏ i = 1 n ( 1 − a i ) ) = ∑ i = 0 n ( − 1 ) i ∑ 1 ≤ j 1 < j 2 < ⋯ < j i ≤ n N ( a j 1 a j 2 … a j i ) N(\prod_{i=1}^n(1-a_i))\\=\sum_{i=0}^n(-1)^i\sum_{1\leq j1<j2<\dots<j_i\leq n}N(a_{j_1}a_{j_2}\dots a_{j_i}) N(i=1n(1ai))=i=0n(1)i1j1<j2<<jinN(aj1aj2aji)由于同学之间相互等价,故上式继续化简:
              = ∑ i = 0 n ( − 1 ) i ( n i ) N ( a 1 a 2 … a i ) ~~~~~~~~~~~~~=\sum_{i=0}^n(-1)^i{n \choose i}N(a_1a_2\dots a_i)              =i=0n(1)i(in)N(a1a2ai)
         f ( i ) = N ( a 1 a 2 … a i ) ~~~~~~~~f(i) = N(a_1a_2\dots a_i)         f(i)=N(a1a2ai)
原式          = ∑ i = 0 n ( − 1 ) i ( n i ) f ( i ) ~~~~~~~~=\sum_{i=0}^n(-1)^i{n \choose i}f(i)         =i=0n(1)i(in)f(i)
考虑 f ( i ) f(i) f(i) 的求法:
对每一种特产采用隔板法,假设某种特产有 s s s 件,根据隔板法,其对应的分配方案数为: ( n − i + s − 1 n − i − 1 ) n-i+s-1 \choose n-i-1 (ni1ni+s1)
f ( i ) = ∏ j = 1 m ( n − i + A j − 1 n − i − 1 ) f(i)=\prod_{j=1}^m {n-i+A_j-1 \choose n-i-1} f(i)=j=1m(ni1ni+Aj1)
通过先预处理出 f f f 代入最终的表达式中即可得到答案。
时间复杂度: O ( n m ) O(nm) O(nm)

Code:

#include <bits/stdc++.h>
using namespace std;

#define int long long
const int mod = 1e9 + 7;

const int N = 2010;
int n, m, f[N], fac[N], infac[N], a[N], ans;

int qpow(int x, int n, const int &mod) {
	int res = 1;
	while (n) {
		if (n & 1) res = res * x % mod;
		x = x * x % mod;
		n >>= 1;
	} return res;
}

void init(int n = N - 10) {
	fac[0] = infac[0] = 1;
	for (int i = 1; i <= n; i++)
		fac[i] = fac[i - 1] * i % mod;
	infac[n] = qpow(fac[n], mod - 2, mod);
	for (int i = n - 1; i >= 1; i--) {
		infac[i] = infac[i + 1] * (i + 1) % mod;
	}
	assert(infac[1] == 1);
}

int binom(int a, int b) {
	if (a < b) return 0;
	return fac[a] * infac[b] % mod * infac[a - b] % mod;
}

signed main() {
	init();
	cin >> n >> m;

	for (int i = 1; i <= m; i++) {
		cin >> a[i];
	}

	for (int i = 0; i <= n; i++) {
		int r = n - i;
		f[i] = binom(n, i);
		for (int j = 1; j <= m; j++) {
			f[i] *= binom(a[j] + r - 1, r - 1) % mod;
			f[i] %= mod;
		}
	}

	for (int i = 0; i <= n; i++) {
		int sg = i % 2 == 0 ? 1 : -1;
		ans += sg * f[i];
		ans %= mod;
	}

	cout << (ans + mod) % mod << "\n";
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值