題目:輸入一序列的正實數和冪次(正整數)對,然後打印結果(具體的比這個精細)
這道題是關於大數計算的(大數求冪),從開始建立思路,到寫代碼、調式到最後被AC以及最終的優化,總共用了差不多一天的時間。開始AC時使用空間500K,時間37MS,最後一次AC空間400K,時間0MS,有很大提高。這主要歸功於加大了每次的數據處理量,減少了重計算次數以及降低循環代碼量。還有就是在使用了二分遞歸,避免了重複計算。不好的一點是代碼量太大,並且使用了太多的變量。
不管怎麼樣,爲這道題付出了很多想法,後來的一些大的優化主要來自對底層的深入理解,代碼的整體實現粒度是很細的,閱讀起來可能會有些困難,但很是值得推敲的,具體實現代碼如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void add(char *s1, char *s2);
char *multi(char *s1, char *s2);
char *result(char *s, int n);
int main(void) {
char ch[100];
char *res;
int num, np;
char tem, *temp1, *temp2;
char *chp;
while ( scanf("%s%d", ch, &num) != EOF ) {
chp = ch + strspn(ch, "0"); // 去掉前導0
temp1 = &ch[strlen(ch)-1];
if ( (temp2 = strchr(ch, '.')) != NULL ) { // 如果有小數點
while ( *temp1 == '0' ) // 去掉小數末尾的0
*temp1-- = 0;
np = strlen(temp2) - 1;
np *= num; // 小數位的num倍是最終結果的小數位數
memmove(temp2, temp2+1, strlen(temp2)); // 去掉小數點
}
else
np = 0; // 整數
res = result(chp, num);
// printf("res: %s\n", res);
temp1 = res + strspn(res, "0");
temp2 = &res[strlen(res) - np]; // 定位小數點
if ( temp1 >= temp2 && np != 0 ) // 如果結果小於1
printf("%c%s\n", '.', temp2);
else if ( np == 0 ) // 如果是整數
printf("%s\n", temp1 == temp2 ? 0 : temp1);
else {
tem = *temp2; // 將該放小數點位置的源字符保存
*temp2++ = 0; // 這裏將源結果字符串斷開,塊式輸出效率高
printf("%s%c%c%s\n", temp1,
'.', tem,
*temp2 == 0 ? "" : temp2);
}
free(res);
}
return 0;
}
char *result(char *s, int n) {
char *res, *ch1, *ch2;
if ( n == 1 )
return multi(s, "1"); // 返回統一類型的可被free掉的數據空間
else if ( n == 2 )
return multi(s, s);
else if ( n > 2 ) {
ch1 = result(s, n >> 1); // 二分遞歸計算
if ( n % 2 != 0 ) {
ch2 = result(s, n - (n >> 1));
res = multi(ch1, ch2);
free(ch2); // result函數返回值得釋放掉
}
else // 如果n是偶數,可避免重複計算
res = multi(ch1, ch1);
free(ch1);
return res;
}
}
char *multi(char *s1, char *s2) {
int i1, i2;
char *ch1, *ch2, *cp1, *cp2, *cp3;
char chp[18];
int i, j, num, dis;
long long j1, j2, j3; // 加大每次計算量
i1 = strlen(s1);
i2 = strlen(s2);
ch1 = (char *)malloc(i1 + i2 + 2); // 1 bit '\0', 1 carry bit(reserved for)
if ( strncmp(s2, "1", 1) == 0 && i2 == 1 ) {
memcpy(ch1, s1, i1+1);
return ch1;
}
ch2 = (char *)malloc(i1 + i2 + 1); // 1 bit '\0'
memset(ch1, '0', i1 + i2 + 2);
ch1[i1+i2+1] = 0;
i = i2;
while ( i > 0 ) {
if ( i >= 8 ) // 和j,每次各可處理8位
dis = 8;
else
dis = i;
i -= dis;
memset(ch2, '0', i1 + i2 + 1); // ch2每次循環都可能被修改
ch2[i1+i2] = 0;
cp1 = &s1[i1];
cp2 = &s2[i2];
cp3 = &ch2[i1 + i]; // i1+i2-(i2-i)=i1+i, 每次循環往左移動dis位,表示和記錄進位
memcpy(chp, cp2 - i2 + i, dis);
chp[dis] = 0;
j2 = atoi(chp);
j = i1;
while ( j > 0 ) {
if ( j >= 8 ) // 最多8位迭代處理與j2相乘
num = 8;
else
num = j;
cp1 -= num;
memcpy(chp, cp1, num);
chp[num] = 0;
j1 = atoi(chp);
memcpy(chp, cp3, dis); // cp3記錄進位,最多有dis位
chp[dis] = 0;
j3 = atoi(chp);
snprintf(chp, 18, "%lld", j1 * j2 + j3);
j1 = strlen(chp);
memcpy(cp3 -j1 + dis, chp, j1); // 數據向右對齊
cp3 -= num; // 定位到下次計算進位可能佔據空間的開頭地址
j -= num;
}
add(ch1, ch2); // 將新的計算結果與前面的相加,最後可獲得最後結果
}
free(ch2);
return ch1;
}
void add(char *s1, char *s2) {
char *cp1, *cp2, *cp3, *ch;
char chp[18];
int num, n1, n2;
long long i, j, k;
s2 += strspn(s2, "0");
n1 = strlen(s1); // make sure n1 > n2
if ( (n2 = strlen(s2)) == 0 )
return;
ch = (char *)malloc(n1+1);
memset(ch, '0', n1);
ch[n1] = 0;
cp1 = &s1[n1];
cp2 = &s2[n2];
cp3 = &ch[n1 - 1];
while ( n2 > 0 ) { // must validate enough memory
if ( n2 >= 16 )
num = 16;
else
num = n2;
cp1 -= num;
cp2 -= num;
memcpy(chp, cp1, num);
chp[num] = 0;
i = atoll(chp);
memcpy(chp, cp2, num);
chp[num] = 0;
j = atoll(chp);
memcpy(chp, cp3, 1);
chp[1] = 0;
k = atoll(chp);
snprintf(chp, 18, "%lld", i + j + k);
i = strlen(chp);
cp3 -= i - 1;
memcpy(cp3, chp, i);
cp3 += i - 1 - num;
n2 -= num;
}
memcpy(s1, ch, n1);
free(ch);
}