轻松周赛赛题:能否被8整除【很有意思的一个数学题】
本文摘抄自:http://www.cnblogs.com/preacher/p/4090102.html
题目链接:http://student.csdn.net/mcs/programming_challenges
题目描述:
给定一个非负整数,问能否重排它的全部数字,使得重排后的数能被8整除。 输入格式: 多组数据,每组数据是一个非负整数。非负整数的位数不超过10000位。 输出格式 每组数据输出一行,YES或者NO,表示能否重排它的全部数字得到能被8整除的数。注意: 重排可以让0开头。
这个题目假如不考虑数据范围比较大会造成严重超时的情况,最容易想到的思路是:将数字全排列,验证是否可被8整除,例如123的全排列:123、231、312、213、132、321 (共3!=6种)。以下为提供全排序处理此问题的方法,当然这种方法网上随处可见:

1 class Program 2 { 3 static int Count = 1; 4 static int DivNum = 8; 5 6 /* 7 * 8 * 1,2: {1,2},{2,1} 9 * 1,2,3: 10 * {3,1,2},{1,3,2},{1,2,3} ( {1,2}中随机插入3 ) 11 * {3,2,1},{2,3,1},{2,1,3} ( {2,1}中随机插入3 ) 12 * 13 * 1,2,3,4: 14 * 就是在上面的六组数字中随机插入4 15 * 16 * 依次类推 17 */ 18 19 static void Main(string[] args) 20 { 21 string[] arr = new string[] 22 { 23 "11223344", 24 "23456798", 25 "89289238", 26 "23458203945829345" 27 }; 28 29 foreach (string item in arr) 30 { 31 if (Perm(item.ToCharArray(), 0)) 32 Console.WriteLine(" {0} Yes", item); 33 else 34 Console.WriteLine(" {0} No", item); 35 } 36 } 37 38 static void Swap(ref Char lc,ref Char rc) 39 { 40 Char tmp = lc; 41 lc = rc; 42 rc = tmp; 43 } 44 45 static bool Perm(char[] arrChar, int cur) 46 { 47 if (cur == arrChar.Length - 1) 48 { 49 long val1 = 0; 50 51 if (long.TryParse(new string(arrChar), out val1) && val1 % DivNum == 0) 52 { 53 return true; 54 } 55 else 56 return false; 57 } 58 else 59 { 60 for (int i = cur; i < arrChar.Length; i++) 61 { 62 Swap(ref arrChar[cur], ref arrChar[i]); 63 if (Perm(arrChar, cur + 1)) 64 return true; 65 Swap(ref arrChar[cur], ref arrChar[i]); 66 } 67 } 68 return false; 69 } 70 }
不过众所周知,n!数量级的算法都比较恐怖一点的(只要数据稍微大一点,常规的数据类型基本难以处理,更何况本题目要处理的是10000的阶乘啊,想想都恐怖)
下面是另一种思路:
其实,这道题使用归纳法比较好,为什么?题目有提示 非负整数的位数不超过10000位 ,这意味着什么呢?非负整数的长度可能达到9999位,如果这么大的数使用全排序,估计计算机要死了,更何况我家自用开发机是8年前的老古董,想的出这种馊主意,它肯定受不了了。所以要借助归纳法,为什么可以用归纳法?呵呵,我猜的,别笑,我说得是实话,我猜测它可以使用归纳法,那么就得找出它遵循什么规律了,下面开始做一些假设过程:
怎么样,简单吧,只要判断末三位组成的数字能被8整除,这个数必然能被8整除。

1 string aaa = "123123213840213849102384192034801923480192381234"; 2 int length = aaa.Length - 3; 3 4 for (int x = 0; x < length; x++)//对应百位 5 { 6 for (int y = 0; y < length; y++)//对应十位 7 { 8 if (y == x) 9 continue; 10 11 for (int z = 0; z < length; z++)//对应个位 12 { 13 if (z == x || z == y) 14 continue; 15 16 if (Convert.ToInt32(new string(new char[] { aaa[x], aaa[y], aaa[z] })) % 8 == 0) 17 { 18 char[] arr = aaa.ToCharArray(); 19 20 Swap(ref arr[x], ref arr[length + 0]); 21 Swap(ref arr[y], ref arr[length + 1]); 22 Swap(ref arr[z], ref arr[length + 2]); 23 24 Console.WriteLine("重排后字符串 : " + new string(arr)); 25 goto GT_1; 26 } 27 } 28 } 29 }
就是枚举百位、十位和个位。
重新审视一下,你会发现上面这个直接枚举的方法还是不够理想:数量级达到10^12,也比较恐怖了的……怎么办呢?
下面是一些优化的思路:
(基于上面说的:想要判断n是否满足n%8==0,只要判断n的末尾3位数字是否能被8整除即可。)
优化思路一:
判断是否可以整除8可以检查(i << 29) 是否等于0;
因为Int32%8==0和Int32<<29==0是同样的效果。 (这个怎么理解呢?其实是这样的: Int32<<29 把前面的29位二进制数移掉,最后的三位如果不等于0就是小于8的数字。毕竟0~7的二进制数才3位嘛,保留后三位其实就是保留了n mod 8的结果。)
这个做法很巧妙,不过呢,据说:操作系统寄存器内的取模运算,也是用的移位。(呵呵似乎这个优化没有太明显的效果)
优化思路二:
先写出从000-999所有能被8乘除的数, 一共125个
然后判断输入的数字中, 是否含有这125个数中的任何一个
比方说, 128能被8整除
那么如果输入的数字中同时含有1,2,8这三个数, 那么这个输入就是符合条件的
嗯,这个思路很好很不错。从编程实现来说呢就是要:
先统计输入的字符串里面0到9各自出现的次数(保存到数组int a[10]当中。)
然后预先把000到999之间能被8整除的那125个数左右存到数组b[ ]当中。
最后,逐一检验b数组的每一个元素的值,判断b[i]的三个位是否都在a数组的统计结果当中有非0个的存在即可。
代码不写了呵呵
话说回来,统计a[ ]的值时,应该不是一定非得扫描完整个字符串。只要a[0]~a[9]的值都>=3就行了。当然,假如扫描完了都没法实现“a[0]~a[9]的值都>=3”,那也不要紧了。直接进入下一步吧
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App