点数和速算问题(续)(有改进)
原贴题目:
有n个骰子,每个骰子m个面,点数从1到m,用这些骰子掷出s点的可能数是多少?
求能在目前的个人电脑上算出n=100万,m=100万,s=n*(m+1)/2时的算法。
即若记所求函数为N(n, m, s), 求可计算N(1000000, 1000000, 500000500000)的有效算法。
我现在的算法能在十几分钟内算到N(10000, 10000, 50005000)。
前贴贴出的算法:
/**
* 指定点数和,计算色子点数的可能排列数.
*
* @param n
* 色子数
* @param m
* 色子面数
* @param s
* 点数和
* @return 排列数
*/
private static BigInteger directN2(int n, int m, int s) {
s -= n;// 参数的点数是从1开始数的,但公式的点数是从0开始。
// 根据对称性降低计算量
if (s > n * (m - 1) / 2) {
s = n * (m - 1) - s;
}
BigInteger N = BigInteger.ZERO;
BigInteger bignPositive = BigInteger.valueOf(n);
BigInteger bignNegative = bignPositive.negate();
TimeMeter tm = new TimeMeter();
BigInteger divisor = BigInteger.ONE;
for (int j = 2; j <= n; j++) {
divisor = divisor.multiply(BigInteger.valueOf(j));
}
tm.reportln("计算除数");
for (int i = 0; i <= s / m; i++) {
// System.out.printf("%dn", i);
BigInteger x = i % 2 == 0 ? bignPositive : bignNegative;
for (int j = s - m * i + 1; j <= s - m * i + n - 1; j++) {
x = x.multiply(BigInteger.valueOf(j));
}
x = x.divide(divisor);
divisor = divisor.multiply(BigInteger.valueOf(i + 1)).divide(BigInteger.valueOf(n - i));
N = N.add(x);
}
return N;
}
改进算法:
/**
* 指定点数和,计算色子点数的可能排列数.
*
* @param n
* 色子数
* @param m
* 色子面数
* @param s
* 点数和
* @return 排列数
*/
private static BigInteger directN5(int n, int m, int s) {
s -= n;// 参数的点数是从1开始数的,但公式的点数是从0开始。
// 根据对称性降低计算量
if (s > n * (m - 1) / 2) {
s = n * (m - 1) - s;
}
BigInteger N = BigInteger.ZERO;
BigInteger bignPositive = BigInteger.valueOf(n);
BigInteger bignNegative = bignPositive.negate();
TimeMeter tm = new TimeMeter();
BigInteger divisor = BigInteger.ONE;
for (int j = 2; j <= n; j++) {
divisor = divisor.multiply(BigInteger.valueOf(j));
}
tm.reportln("计算除数");
for (int i = 0; i <= s / m; i++) {
// System.out.printf("%dn", i);
BigInteger x = i % 2 == 0 ? bignPositive : bignNegative;
// 改一个个累乘为两两归并相乘
BigInteger[] xs = new BigInteger[n - 1];
for (int j = 0; j < xs.length; j++) {
xs[j] = BigInteger.valueOf(j + s - m * i + 1);
}
for (int k = 0; k < 32 - Integer.numberOfLeadingZeros(xs.length); k++) {
for (int j = 0; (j + 1) << k < xs.length; j += 2) {
xs[j << k] = xs[j << k].multiply(xs[(j + 1) << k]);
}
}
x = xs[0].divide(divisor);
divisor = divisor.multiply(BigInteger.valueOf(i + 1)).divide(BigInteger.valueOf(n - i));
N = N.add(x);
}
return N;
}
在我的计算机上计算N(10000, 10000, 50005000)的结果:
改进前:1832秒
改进后: 652秒
效果还是比较明显的。不过不是数量级的提升,不太满意。评分完成:已经给 孤魂浪子 加上 200 银元!
|