首先说一个概念,任意自然数N,其各位数字相加之和为M,那么N与M对模3同余。根据同余定理,可以先对N的每一位数字除以3取余,再相加后除以3取余,结果依然和N直接除以3取余相同。
举个例子1234567除以3余1,1+2+3+4+5+6+7 = 28除以3余1。分别对计算1-7除以3的余数再相加,得到1+2+0+1+2+0+1 = 7除以3余1。
这里的数学原理其实很简单,我们令个位数字是第0位,十位数字是第1位,百位数字是第2位,以此类推。假设自然数N的第i位数字是n,那么这位数字n实际代表的数是n乘以10的i次方。注意到10的i次方减去1得到999...99(i个9)必然被3整除,因此10^i 除以3必然余数是1。那么n * 10^i 与n对模3同余。相信聪明的小伙伴已经发现了,把模3改成模9,可以得到相同的结论。
好了,数学原理分析到这里,接下来开始解题。首先创建一个数组vMod,vMod[i]表示原数各位数字中除以3余数为i的个数。那么(vMod[1] + vMod[2] * 2) % 3就是原数除以3的余数,记为m。接下来具体问题具体分析。
【1】若m为0,即原数为3的倍数,那么依次去掉数字0,3,6,9这些3的倍数的数字即可,ans = vMod[0]。这里注意边界条件,若全部数字都是3的倍数,那么当剩下最后一个数字时就不能再去掉了,答案修正为ans - 1。
【2】若m不为0,即原数不是3的倍数,此时m只能是1或2。那么第一步我们需要去掉某个数,使得剩下的数满足%3 == 0的条件,接下来再依次去掉数字0,3,6,9这些3的倍数的数字即可,ans = vMod[0] + 1。注意!!!这里去掉的某个数可能并不存在,此时ans = 0。判断的依据就是vMod[m]的值是否大于0。举个例子119999,这里的m = 2,vMod[2] = 0,因此答案为0。
然后我们再分析【2】的边界条件。这里就比较恶心了,有2个边界条件。第一个条件和【1】中类似,若最后ans等于原数各位数字的个数,那么当剩下最后一个数字时就不能再去掉了,答案修正为ans - 1;第二个条件,若vMod[m] = 1,且对应的数字正好是原数的最高位数字,就要考虑前导0的情况。举例2001497,这里m = 2,第一次去除数字只能去除最高位2,此时该数字就变成了1497,还能再删除9,最终ans = 2。观察发现vMod[0] + 1 = 4,和ans正好相差2,这个数字正好是最高位去掉后前导0的个数。因此这种情况下ans = vMod[0] + 1 - 最高位后前导零个数。
好了,大功告成,提交~
结果……没有AC。
原因是,当【2】的两个边界条件结合起来时,情况又会发生变化。举例数字10003,根据公式前导零个数为3,因此ans = vMod[0] + 1 - 3 = 2。实际情况是,ans = 1,当首位的1去除后,剩下数字是3,不能再去除了。类似的数字2000,使用公式计算ans = 1,而实际ans = 0。
找到问题后就简单了,首先ans = vMod[0] + 1,判断ans的值是否等于原数各位数字的个数,若是则修正ans = ans - 1。接下来再判断vMod[m] = 1,且对应的数字正好是原数的最高位数字的情况,ans = ans - 最高位后前导零个数。
下面贴出代码。
#include <iostream> #include <vector> using namespace std; int main() { int t; cin >> t; string n; while (t--) { cin >> n; vector<int> vMod(3, 0);//vMod[i]表示原数各位数字中除以3余数为i的个数 for (char& c : n) { ++vMod[(c - '0') % 3]; } int m = (vMod[1] + vMod[2] + vMod[2]) % 3; //原数除以3的余数 int ans; if (m == 0) { //原数为3的倍数 ans = vMod[0]; if (ans == n.size()) --ans; } else { //原数不是3的倍数 if (vMod[m] == 0) { //没有任何一位数字跟原数模3同余 ans = 0; } else { ans = vMod[0] + 1; //先删掉同余数字,再删余数为0的 if (ans == n.size()) --ans; if (vMod[m] == 1 && (n[0] - '0') % 3 == m) { for (int i = 1; i < n.size(); ++i) { if (n[i] != '0') break; --ans; } } } } cout << ans << endl; } }