来自北京大学ACM的一道题目:http://poj.org/problem?id=1001&lang=zh-CN&change=true。
对于题目中的高精度幂,现成的应该没有哪一种数据类型可以直接进行运算(ps:好像Java有BigInteger 这种类可以,往后再多了解这个)。那该怎么办呢?
大一那时候的想法是分段而行。怎么样分?例如 (1111111111)2 ,可以转换为(11111*105+11111)2之流。但后来觉得不太可行,因为这会涉及到很多的10n,需要很多的标记,似乎也不好操控,特别是分得很细的时候。第二个想法是数组。但是遇到的问题还是从前一个想法继承下来了。后来也慢慢断绪了。
现在的这个想法的产生,应该说很大程度是归结于编程和语言操控能力的提高。因为算法的实现是基于很简单的道理。
1. 还是那样,大事化小:是分段。不过不再是基数,而是平方次数。
2. 乘法转加法:基于数组的矩阵使用,摒弃10n的实现。
现在单举整数为例。因为整数明白了,小数也只是多点细节的东西而已。
例如 (95123)12,ACM题目上的第一个数。
首先,把12分解。为什么?(95123)12 = ((951232 * 95123)2)2 =(95123)1+1+1+3+6 。
目的很简单,就是构造尽可能多的2次方次数。至于为什么要构造2次方呢,是因为2次方下,乘数于被乘数相等,可以省许多工作(你只需要记得当前的次方数,和原本的1次方数,至少在这个算法看来是这样。)。
矩阵的应用,是对大大相乘的削减,取代为小乘小加。看图:
9 | 5 | 1 | 2 | 3 | |
9 | 81 | 45 | 9 | 18 | 27 |
5 | 45 | 25 | 5 | 10 | 15 |
1 | 9 | 5 | 1 | 2 | 3 |
2 | 18 | 10 | 2 | 4 | 6 |
3 | 27 | 15 | 3 | 6 | 9 |
951232 = 9048385129
乘法的本质是什么呢?加法?前一步。于是你发现了:
9 | 5 | 1 | 2 | 3 | |
9 | 81 | 45 | 9 | 18 | 27 |
5 | 45 | 25 | 5 | 10 | 15 |
1 | 9 | 5 | 1 | 2 | 3 |
2 | 18 | 10 | 2 | 4 | 6 |
3 | 27 | 15 | 3 | 6 | 9 |
9,是951232的个位数。
6+6,是951232的十位数,减去进位的10,也就是2。
…
…
最后求出第一次的平方和。
这就是该算法的核心了。由此不断累积,得到更高阶的幂。往后的工作,也就是重复这种运算,实践这种思想。
该算法好不好?
以作者的身份来说,它的原理很坦白。而在时空复杂度的问题上,设计到大数组的操作,for循环的大量使用,时空复杂度应该不小,有待进一步优化。
(注:另外,short类型的最大值为32767,在最大斜线上32767/81=404。 也就是说,最大支持的数不能大于999..9(404个9)。)
后记:
很多时候吧,得出一个想法也许不是相像那么难。因为随着社会阅历的增加,更方面知识的扩展,只要注重积累,每个难题都有可能会找到它的原型。而也是很多时候,解决这些有想法的问题时,做起来可还挺费劲的。所以还真要多实践实践。(ps:多积累思想,多实践技术)
附上 实现源码(Java实现):
public class HighAccuracy {
private byte n; // times of square
short[] each; // to store each short in inputNum
private short dot = 0; // original location of dot
public HighAccuracy(String num, byte n){
this.n = n;
getOriginalList_From_InputNum(throwUselessZero(num));
}
public void showResult(){
short[][] matrix = this.toGet_Matrix();
short[] shorts = this.getMatrixToShortList(matrix);
for(int index=0; index<shorts.length; index++){ //检索 小数点dot 的位置
if(index==0 && shorts[0] == 0){
System.out.print(".");continue;
}
if(shorts[0]!=0 && index==(shorts.length-this.dot*this.n))
System.out.print(".");
System.out.print(shorts[index]);
}
System.out.println();
}
private String throwUselessZero(String inputNum){
if(inputNum.charAt(inputNum.length()-1)=='0'){
int i=0;
while(inputNum.charAt(inputNum.length()-(++i))=='0');
if(inputNum.charAt(inputNum.length()-(i+1))=='.') i++;
return inputNum.substring(0, inputNum.length()-i+1);
}
return inputNum;
}
private void getOriginalList_From_InputNum(String inputNum){
char[] eachChar = inputNum.toCharArray();
if(inputNum.contains("."))
each = new short[inputNum.length()-1];
else
each = new short[inputNum.length()];
for(int i=0,j=0; i<inputNum.length(); i++){
if(eachChar[i] != '.'){
each[j++] = (short)(eachChar[i]-48);
}else{
dot = (short)((inputNum.length()-1) - i); //to get the dot's location
}
}
}
private byte[] get_n_list(byte t){
byte[] list = new byte[8];
int i = 0;
while( t>=1 ){
if(t%2 == 1){
list[i++] = 1;
t-=1;
}
if((t = (byte)(t/2))== 1){
list[i++] = 1;
list[i] = 1;
}else{
if(t != 0)
list[i++] = t;
}
}
return list;
}
private short[][] toGet_Matrix(){
short[][] matrix = null;
short[] previous = each;
short[] current = each;
byte[] nbytes = this.get_n_list(this.n);
for(int i=nbytes.length-1; i>=0; i--){ // 根据 前一矩阵和序列 和当前矩阵和序列 得到 当前矩阵
// previous 指向 当前矩阵 得出的求和序列
// current 指向 下一个矩阵和序列, 根据 nbytes 得出
if(nbytes[i] != 0){ //reach the end of nList
if(nbytes[i+1]==0)i--;
matrix = new short[previous.length][current.length]; System.out.println();
for(int a=0; a<matrix.length; a++){
for(int j=0;j<matrix[0].length;j++){
matrix[a][j] = (short)(previous[a]*current[j]);
}
}
if(i != 0){
previous = this.getMatrixToShortList(matrix);
if(nbytes[i-1] == 1){
current = each;
}else{
current = previous;
}
}
}
}
return matrix;
}
private short[] Once_Matrix_Sum(short[][] matrix_parameter){
int matrix_x = matrix_parameter.length;
int matrix_y = matrix_parameter[0].length;
short[] matrix_sum = new short[matrix_x+matrix_y-1];
// 各位(个十百千万...)求和: 矩阵 反斜 求和
// 大乘 转 小加 运算
for(int index=0; index<matrix_sum.length ; index++){
matrix_sum[index] = 0;
int moveStep_x = index+1;
int moveStep_y = 1;
int xx = matrix_x-moveStep_x;
if(xx<0){
xx=0;
moveStep_y = index+2-matrix_x;
}
for( ; moveStep_y<=(index+1) && moveStep_y<=matrix_y; moveStep_x--,moveStep_y++ ){
if(moveStep_x > 0){
matrix_sum[index] += matrix_parameter[xx++][matrix_y-moveStep_y];
}
}
}
return matrix_sum;
}
private short[] getMatrixToShortList(short[][] matrix_parameter){ // get the correct short list
short[] matrix_sum = Once_Matrix_Sum(matrix_parameter);
for(int i=0; i<matrix_sum.length-1; i++){ //最高位除外,其他位置 向上进位
if(matrix_sum[i]>9){
matrix_sum[i+1] += (short)((matrix_sum[i]/10));
matrix_sum[i] = (short)((matrix_sum[i]));
}
}
short[] matrix_sum_;
int index;
if(matrix_sum[matrix_sum.length-1] > 9){ // 判断最高位 是否有进位产生
matrix_sum_ = new short[matrix_sum.length+1]; // 由此 判断 创建的 short[] 数组是否长度+1
matrix_sum_[0] = (short)(matrix_sum[matrix_sum.length-1]/10);
matrix_sum_[1] = (short)(matrix_sum[matrix_sum.length-1]);
index = 2;
}else{
matrix_sum_ = new short[matrix_sum.length];
matrix_sum_[0] = matrix_sum[matrix_sum.length-1];
index = 1;
}
// 等到 真正的求和序列
for(int i=1; i<matrix_sum.length; i++,index++){
matrix_sum_[index] = matrix_sum[matrix_sum.length-1-i];
}
return matrix_sum_;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
String numStr = "95.123";
byte times = (byte)12;
HighAccuracy ha = new HighAccuracy(numStr, times);
ha.showResult();
}
}