算法习题---3.08循环小数(UVa202)
一:题目
输入整数a和b(0<=a<=3000,1<=b<=3000),输出a/b的循环小数表示以及循环节长度。 例如,a=5,b=43,小数表示为0.(116279069767441860465),循环字节长度为21
当循环节长度超过50时,后面使用...代替,不进行输出后面部分
(一)样例输入
76 25 5 43 1 397
(二)样例输出
76/25 = 3.04(0) 1 = number of digits in repeating cycle 5/43 = 0.(116279069767441860465) 21 = number of digits in repeating cycle 1/397 = 0.(00251889168765743073047858942065491183879093198992...) 99 = number of digits in repeating cycle
二:题目分析
(一)余数用作判断循环节的依据:(循环节长度大小不会超过余数大小,原因如下:)
1/6-->10/6=1...4 4/6-->40/6=6...4出现两次4,所以一定会出现循环节 4/6-->40/6=6...4出现三次4,所以循环节就在二三次之间(即商6)
所以用一个数组记录余数,大小不会超过除数大小
(二)同时也要记录商,用于记录循环节
若是除数为P,则余数为0->P-1;所以在P次之间一定会出现两个相同余数 //(一中原因)
-->一定会出现循环节
所以我们再记录一次余数,当出现某个余数出现为3次,那么循环节在两个余数位置中间
由于商数组和余数数组对应,所以可以轻松找到循环节
(三)商数组和余数数组大小
余数数组大小应该为除数大小 //余数只需要除数大小,用于记录余数出现次数
商数组大小应该为除数大小的两倍 //商用于记录对应余数中的商值,由于每一个余数必须出现3次,所以我们记录的商必须足够长,足以支持余数次数到达3
例:
其中0<=a<=3000,1<=b<=3000---> 所以余数数组不会超过3000 商数组大小不超过6000
注:上面的商和余数数组只记录小数点后的值,不记录整数值
三:代码分析(使用76/25 ,1/6 ,5/43分析)
(一)处理整数部分:只剩下会出现小数部分的被除数进行下一步处理
//开始处理数据 //1.获取整数 //先记录整数:可以在记录时直接输出,节省篇幅 printf("%d/%d = ", a, b); if (!(a / b)) printf("0"); else while (a / b) { printf("%d", a / b); a %= b; } printf(".");
(1)76/25
76/25=3...1
代码输出76/25 = 3.
(2)1/6
直接为小数,所以按照代码输出1/6 = 0.
(3)5/43
直接为小数,所以按照代码输出5/43 = 0.
(二)获取商数组和余数数组
//2.开始记录商和余数 i = 0; while (1) //例如:1/6 { //记录商和余数 res[i] = a * 10 / b; //10/6=1 //1.记录商 rem[a * 10 % b]++; //10%6=4 //2.记录对应余数位置出现次数 //注意两次和三次都是只记录一次即可 if (rem[a * 10 % b] == 2&&!two_flag) //3.若是出现两次,则开始进行记录 只记录第一个出现两次的余数 { r_s = i; two_flag = 1; } if (rem[a * 10 % b] == 3) //4.若是出现3次,则确定了循环节位置,直接跳出 { r_e = i; break; } //处理被除数 a = a * 10 % b; //a = 4 i++; }
(1)76/25
10/25=0...10 0 1 第一个为商下标,第二个为余数次数 100/25=4...0 1 1 0/25=0...0 2 2 余数出现两次时的商0和出现一次时的商4不一致 0/25=0...0 3 3
res = { 0 4 0 0} rem = { 3 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0}
(2)1/6
10/6=1...4 0 1 第一个为商下标,第二个为余数次数 40/6=6...4 1 2 余数出现两次时的商6和出现一次时的商1不一致 40/6=6...4 2 3
res = { 1 6 6 } rem = { 0 0 0 0 3 0 }
(3)5/43
50/43=1...7 0 1 70/43=1...27 ... .....=1...7 21 2 余数出现两次时的商和出现一次时的商一致 ... .....=1...7 42 3
(三):进行数据输出
注意记录数据时要区分0.1(6) 3.04(0) 和0.(11....) 不同之处在于循环节是不是从小数第一位开始 处理原则在于看1,2两次余数相同时,商是否一致(上面案例)
int rep_count = r_s; if (res[r_s] != res[2 * r_s - r_e]) //若是余数第一次和第二次对应的商值不一样,则加一 rep_count++; for (j = 0; j <rep_count ;j++) //注意,先看下面注释,可以知道,真正循环节结束在我们获取的第二个相同余数位置-->即r_s { if (j == 2 * r_s - r_e && rep_count==r_s) //注意这里:不是j-r_s printf("("); else if (j == 2 * r_s - r_e+1&&rep_count==r_s+1) //遇到循环节不是从小数点后开始,则向后一位 printf("("); printf("%d", res[j]); if (j +r_e- 2*r_s == 49) //不是j-r_s -->因为我们虽然确定r_s到r_e时一个循环节,但是r_s是第二个相同余数开始,r_e是第三个余数开始 { //4 4 4--->r_s是第二个开始,r_e是第三个开始,但是真的循环节是从第一个4到第二个4 printf("..."); break; } } printf(")\n"); printf(" %d = number of digits in repeating cycle\n", r_e - r_s);
四:代码实现
void test33() { int a, b; int zs[4]; //存放整数,例如结果10.13161616,这里用于存放10 //由于程序变更,后面没有用到zs,但是为了便于理解,留在这 int res[MAX_REP]; //存放商 int rem[MAX_REP]; //存放余数 int two_flag; //注意两次和三次都是只记录一次即可,但是由于三次直接break,所以不做标志 int i, j; int r_s, r_e; //循环节开始,循环节结束 FILE* fi = freopen("data2.in", "r", stdin); freopen("data2.out", "w", stdout); while (!feof(fi)) { scanf("%d", &a); scanf("%d", &b); memset(zs, 0, sizeof(zs)); memset(res, 0, sizeof(res)); memset(rem, 0, sizeof(rem)); two_flag = 0; //开始处理数据 //1.获取整数 //先记录整数:可以在记录时直接输出,节省篇幅 printf("%d/%d = ", a, b); if (!(a / b)) printf("0"); else while (a / b) { printf("%d", a / b); a %= b; } printf("."); //2.开始记录商和余数 i = 0; while (1) //例如:1/6 { //记录商和余数 res[i] = a * 10 / b; //10/6=1 rem[a * 10 % b]++; //10%6=4 //注意两次和三次都是只记录一次即可 if (rem[a * 10 % b] == 2&&!two_flag) //若是出现两次,则开始进行记录 { r_s = i; two_flag = 1; } if (rem[a * 10 % b] == 3) { r_e = i; break; } //处理被除数 a = a * 10 % b; //a = 4 i++; } //开始记录数据 //注意记录数据时要区分0.1(6) 3.04(0) 和0.(11....) //不同之处在于循环节是不是从小数第一位开始 //处理原则在于看1,2两次余数相同时,商是否一致 int rep_count = r_s; if (res[r_s] != res[2 * r_s - r_e]) rep_count++; for (j = 0; j <rep_count ;j++) //注意,先看下面注释,可以知道,真正循环节结束在我们获取的第二个相同余数位置-->即r_s { if (j == 2 * r_s - r_e && rep_count==r_s) //注意这里:不是j-r_s printf("("); else if (j == 2 * r_s - r_e+1&&rep_count==r_s+1) //遇到循环节不是从小数点后开始,则向后一位 printf("("); printf("%d", res[j]); if (j +r_e- 2*r_s == 49) //不是j-r_s -->因为我们虽然确定r_s到r_e时一个循环节,但是r_s是第二个相同余数开始,r_e是第三个余数开始 { //4 4 4--->r_s是第二个开始,r_e是第三个开始,但是真的循环节是从第一个4到第二个4 printf("..."); break; } } printf(")\n"); printf(" %d = number of digits in repeating cycle\n", r_e - r_s); } freopen("CON", "r", stdin); freopen("CON", "w", stdout); }