CODE JAM APAC 2015 round B: Problem A. Password Attacker(小球放进盒子的计数问题)

#include <fstream>
using namespace std;
#define MOD 1000000007
long long solve[100][100];

int main() {
	ifstream fin("A-large-practice.in");
	ofstream fout("output.txt");
	solve[0][0] = 1;
	for (int i = 1; i < 100; i++) {
		solve[i][0] = 1;
		solve[i][i] = (solve[i-1][i-1] * (i+1)) % MOD;
		for (int j = 1; j < i; j++) {
			solve[i][j] = ((solve[i-1][j-1] + solve[i-1][j]) * (j+1)) % MOD;
		}
	}
	int T, M, N;
	fin >> T;
	for (int t = 1; t <= T; t++) {
		fin >> M >> N;
		fout << "Case #" << t << ": " << solve[N-1][M-1] << endl;
	}
	return 0;
}

参加了这一轮APAC,结果只做对这第一题,而且解法还是临时在网上查的,内牛满面。这一轮题目绝对比上一轮简单,需要代码量明显少,只恨自己高中排列组合、数学归纳法没学好,书到用时方恨少啊!

题目链接:http://code.google.com/codejam/contest/4214486/dashboard,截图:



分析:这是一道典型的排列组合题,M个不同字符组成长度为N的字符串,N>=M,这M个字符至少各出现一次,求有多少种不同的组合方式(为了防止溢出,结果需要mod 10000007)。这种题目我从高中时就碰到过,高中就不会做,到了研三依旧不会做。。。解法是临时百度的,看的参考文献截图如下:


这个三角形,N代表行,M代表列,用函数S(N,M)描述该三角形存储值,比方说S(4,3) = 6。该三角形满足递推公式:S(N, 1) = S(N, N) = 1 且其余S(N+1, M+1) = S(N, M) + S(N, M+1)  * (M + 1)。最后的解法数量为:M! * S(N,M),例如N = 4, M = 3的解法有36个。在编程时可以用数组来存放这个三角形,注意之前描述时N,M都是从1开始记的,而数组下标从0开始记,所以存在一个加减一的细节。由于这道题1<=M<=N<=100,却要做100个test的要求,并且求S(N,M)依赖于S(N-1,M-1),可以先把M<=N<=100的数组作为全局变量求出来,然后直接求数组索引就能最快地得到全部解。

当时也不懂原理,稀里糊涂把程序编出来通过,第二天才想明白为什么这么算。这道题是典型的把N个不同球放进M个不同盒子,并且每个盒子不能为空的问题。这里球数代表字符串中的字符位置(1~N),而盒子数则代表M个不同的字符(1~M),上述三角形的内容是将N个不同球放进M个相同盒子的解法数,所以要转换成M个不同盒子还需乘上M!。其递推公式解释为:

S(N, 1) = S(N, N) = 1//把N个球放入一个盒子只有一种解法,把N个球放入N个相同盒子也只有一种解法(每个盒子放一个球)

S(N+1, M+1) = S(N, M) + S(N, M+1)  * (M + 1)//把N+1个球放进M+1个盒子可以分两种做法:1.将前N个球放进M个盒子,然后第N+1个球单独放一个盒子,或者2.将前N个球放进M+1个盒子,将第N+1个球放进这M+1个盒子的任意一个,做法有S(N, M+1)  * (M + 1)种。这样就能得到上面的三角形了。又因为盒子(字符)实际上是有区别的,因此最后结果还要乘以M!。

如果从一开始就把盒子看做不同,那么数组的计算更加简单,result(N+1, M+1) = (result(N, M) + result(N, M+1)) * (m+1),递推方式与上面类似。

最后程序为:


#include <fstream>
using namespace std;
#define MOD 1000000007
long long solve[100][100];

int main() {
	ifstream fin("A-large-practice.in");
	ofstream fout("output.txt");
	solve[0][0] = 1;
	for (int i = 1; i < 100; i++) {
		solve[i][0] = 1;
		solve[i][i] = (solve[i-1][i-1] * (i+1)) % MOD;
		for (int j = 1; j < i; j++) {
			solve[i][j] = ((solve[i-1][j-1] + solve[i-1][j]) * (j+1)) % MOD;
		}
	}
	int T, M, N;
	fin >> T;
	for (int t = 1; t <= T; t++) {
		fin >> M >> N;
		fout << "Case #" << t << ": " << solve[N-1][M-1] << endl;
	}
	return 0;
}

程序非常短,执行速度也基本上是最快的

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值