2017.10.6 BJOI2015 bzoj4338 糖果

  糖果

题目背景:

bzoj4338

分析:组合数

呜呜呜,为什么你们都推的出组合数······

本废喵死都没有推出组合数······

所以我们来详细考虑下这道题目:

首先,我们可以知道,如果我们求出填写一行的方式一共有t种,那么显然的,最后的答案就应该是P(t, n)即,t! / (t  n)!,所以我们要求取t,先把原题进行转化,

 

现在我们有k个珠子,然后有m个相同隔板,每一个隔板都放在某一个珠子的后面(一个珠子后面可以有多个隔板),一共有多少种不同的放法?

 

现在证明这两个问题为什么等价,我们可以发现,对于每一种放置的方法,都存在一个长度为m的序列aa[i]表示第i个隔板前面是第几个珠子,隔板的顺序按其前面的珠子的编号来排

举个例子:当k = 3 m = 3

case 1:

 

代表方案:1 1 1

 

case 2:


代表方案:1 1 2

 

case 3:

 

代表方案:1 1 3

 

case 4:

 

代表方案:1 2 2

 

case 5:

 

代表方案:1 2 3

 

case 6:

 

代表方案:1 3 3

 

case 7:

 

代表方案:2 2 2

 

case 8:

 

代表方案:2 2 3

 

case 9:

 

代表方案:2 3 3

 

case 10:

 

代表方案:3 3 3

一共10种方案,和10种图像一一对应,转化成立。

现在,我们得到了一个更加像组合数的题目,但是现在好像依然不好做,然后这一发我们再来转化一个题

 

我们有m + k个珠子,然后有m个隔板,每一个隔板可以放在编号2 ~ m + k的一个珠子后面,每个珠子后面只能够有一个隔板。

 

再来证明一下这个问题和上一个等价,现在因为我们保证了每个珠子后面只会有一个隔板,那么很显然的每两个隔板之间,必定有大于等于一个珠子,然后我们删去,每个隔板前面的那一个珠子,剩下的就是k个珠子和m个隔板,也就对应了上面一个转化问题的一种方案,也就应该对应了原题的一种方案,这个自己手动画图思考一下就好,我就只举两个例子了,

 

è

对应原题方案1 2 3

 

è

对应原题方案2 3 3

 

然后为什么只放2 ~ m + k后面呢,因为不然的话,放在以后面,删掉这个隔板前面的1,第一个隔板前面就没有东西了······

所以,我们相当于在 m + k - 1个位置中,选择出m个,所以最后获得的结论就是 t = C(m + k  1, m)

有一种大功告成的喜悦····

 

感觉上,我们如果预处理一下阶乘和阶乘逆元,我们就可以得到60分了,是不是很开心,60分就是一个组合数取模,(然后本废喵什么都不知道,好了我们来解决最后的一步,组合数对合数取模,首先听说有个什么扩展lucas···本人表示我不懂····这个题,完全有更加暴力简单的做法,考虑,暴力分解因式,暴力分解出1 ~ m当中每一个质因数的有多少次方,直接先预处理1 ~ m当中的质数,然后暴力nloglogn 暴力枚举每一个质数的倍数然后除下去就好,然后因为我们知道,组合数肯定不可能出现分数,那么我们可以暴力在m + k - 1 ~ k当中除下来,因为我们知道小于等于a的最大的b的倍数,一定是 a - a % b 那么我们就可以找到每一个质数小于m + k - 1的第一个倍数,然后枚举后面的倍数把这个质数在1 ~ m中的次方除下去即可,最后暴力相乘取模就好了,好了,终于完结啦,撒花······(如果有不清楚的地方,参见代码

 

Update

前段时间某dalao去请教了一下数竞dalao,发现了一种更好解释这个组合数的方法我们相当于在一个长度为m的数组当中填1 ~ k当中的一个数,然后,要单调不减,即数组a长度为m,对于i(i >= 1&& i <= m) a[i] >= a[i - 1], 然后定义b[i] = i + a[i],然后我们可以发先b[i] 严格大于 b[i - 1]i > i  1, a[i] >= a[i - 1])然后考虑b[i] 的取值范围,因为第一个数,一定大于等于2,最大的数小于等于m + k,所以可以取的数位2 ~ m + k, 共有m + k - 1种选择,选择m个的方案数为C(m + k - 1, m)

Source

/*
	created by scarlyw
*/
#include <cstdio>
#include <string>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <cmath>
#include <cctype>
#include <vector>
#include <set>
#include <queue>
#include <ctime>

inline char read() {
	static const int IN_LEN = 1024 * 1024;
	static char buf[IN_LEN], *s, *t;
	if (s == t) {
		t = (s = buf) + fread(buf, 1, IN_LEN, stdin);
		if (s == t) return -1;
	}
	return *s++;
}

///*
template<class T>
inline void R(T &x) {
	static char c;
	static bool iosig;
	for (c = read(), iosig = false; !isdigit(c); c = read()) {
		if (c == -1) return ;
		if (c == '-') iosig = true;	
	}
	for (x = 0; isdigit(c); c = read()) 
		x = ((x << 2) + x << 1) + (c ^ '0');
	if (iosig) x = -x;
}
//*/

const int OUT_LEN = 1024 * 1024;
char obuf[OUT_LEN], *oh = obuf;
inline void write_char(char c) {
	if (oh == obuf + OUT_LEN) fwrite(obuf, 1, OUT_LEN, stdout), oh = obuf;
	*oh++ = c;
}

template<class T>
inline void W(T x) {
	static int buf[30], cnt;
	if (x == 0) write_char('0');
	else {
		if (x < 0) write_char('-'), x = -x;
		for (cnt = 0; x; x /= 10) buf[++cnt] = x % 10 + 48;
		while (cnt) write_char(buf[cnt--]);
	}
}

inline void flush() {
	fwrite(obuf, 1, oh - obuf, stdout);
}

/*
template<class T>
inline void R(T &x) {
	static char c;
	static bool iosig;
	for (c = getchar(), iosig = false; !isdigit(c); c = getchar())
		if (c == '-') iosig = true;	
	for (x = 0; isdigit(c); c = getchar()) 
		x = ((x << 2) + x << 1) + (c ^ '0');
	if (iosig) x = -x;
}
//*/

const int MAXN = 100000 + 10;

long long n, m, k, mod, prime_cnt;
long long up[MAXN], down[MAXN], c[MAXN], prime[MAXN];
bool not_prime[MAXN];

inline void seive() {
	not_prime[1] = true;
	for (int i = 2; i < MAXN; ++i) {
		if (!not_prime[i]) prime[++prime_cnt] = i;
		for (int j = 1; j <= prime_cnt && prime[j] * i < MAXN; ++j) {
			not_prime[i * prime[j]] = true;
			if (i % prime[j] == 0) break ;
		}
	}
}

int main() {
	R(n), R(m), R(k), R(mod), seive();
	for (long long i = 1; i <= m; ++i) down[i] = i, up[i] = m + k - i;
	for (long long i = 1; i <= prime_cnt; ++i) {
		long long x = prime[i], cnt = 0;
		for (long long j = x; j <= m; j += x)
			while (down[j] % x == 0) cnt++, down[j] /= x;
		c[i] = cnt;
	}
	for (long long i = 1; i <= prime_cnt; ++i) {
		long long x = prime[i], s = m + k - 1LL- (m + k - 1LL) % x;
		for (long long j = m + k - s; j <= m; j += x) {
			while (c[i] && up[j] % x == 0) up[j] /= x, c[i]--;
			if (c[i] == 0) break ;
		}
	}
	long long sum = 1, ans = 1;
	for (long long i = 1; i <= m; ++i) sum = sum * up[i] % mod;
	for (long long i = 0; i < n; ++i) ans = ans * (long long)(sum - i) % mod;
	std::cout << ans;
	return 0;
}


 

 

 

 

 

 

 

 


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值