#9 Palindrome Number

本文探讨如何在不使用额外空间的情况下,判断整数是否为回文数,提供了一些提示来帮助理解问题,并介绍了几种解决方法。

题目链接:https://leetcode.com/problems/palindrome-number/


Determine whether an integer is a palindrome. Do this without extra space.

click to show spoilers.

Some hints:

Could negative integers be palindromes? (ie, -1)

If you are thinking of converting the integer to string, note the restriction of using extra space.

You could also try reversing an integer. However, if you have solved the problem "Reverse Integer", you know that the reversed integer might overflow. How would you handle such case?

There is a more generic way of solving this problem.




<think>根据用户查询,用户想了解XTUOJ平台(湘潭大学在线评测系统)上关于“完美回文数”的相关内容,包括题目和解题思路。用户提供了几个引用,其中引用[1]和[2]定义了“完美回文数”,引用[3]和[4]是两段C语言代码。 从引用[1][2]中,我们了解到“完美回文数”的定义: - 十进制无前导0的整数 - 有偶数位数 - 只含有0和1的数码 - 是回文数(从左到右和从右到左读一样) 举例:1001, 11 是;12,1010,1,101 不是。 问题:给定整数n,求n等于最少多少个完美回文数的和。 引用[3]和[4]是两段不同的代码,都是解决这个问题的,但思路不同。 引用[3](豆包写的代码): - 使用了动态规划(完全背包)的方法。 - 定义了三个完美回文数:v[3] = {1111, 1001, 11}。 - 状态定义:f[i][j] 表示使用前i种完美回文数(即前i种物品)凑出金额j所需的最少个数。 - 初始化:f[0][0]=0,其他为无穷大。 - 状态转移:对于每种物品,枚举使用次数k,更新状态。 - 最后,对于每个查询n,如果f[3][n]为无穷大或者n不能被11整除(因为每个完美回文数都是11的倍数?),则输出0(可能表示无解?),否则输出f[3][n]。 注意:题目中定义的完美回文数有无穷多个,但这里只用了三个:11,1001,1111。为什么只用这三个?因为任何完美回文数都可以由这三个线性组合而成?实际上,我们可以证明:任何一个完美回文数都可以表示成11乘以一个由0和1组成的回文数(但这里1001=11*91,1111=11*101)。而且,由于完美回文数都是11的倍数,所以n必须被11整除,否则无解。 引用[4](XTU OJ best回文数): - 首先判断n%11!=0,则直接输出0。 - 否则,令cnt = n/11,问题转化为求cnt由多少个由1,91,101(因为11*1=11, 11*91=1001, 11*101=1111)的和(个数最少)。 - 然后使用三重循环枚举使用1、91、101的个数(i, j, k),使得 i + j*91 + k*101 = cnt,并记录总个数(i+j+k)的最小值(这里代码实际上是找第一个可行的组合,然后输出总个数,并不是求最小个数,但题目要求最少个数,所以这应该是错误的?而且三重循环可能会超时)。 实际上,引用[4]的代码有逻辑错误:它没有求最小个数,而是找到第一个满足的组合就输出,然后break。另外,三重循环的枚举范围也有问题(j*91<=cnt,k*101<=cnt),但i的枚举范围是0到cnt,这样效率很低(cnt最大可能是n/11,n最大100000,则cnt最大约9000多,三重循环可能达到9000*100*100,即9000万,超时)。 因此,正确的思路应该是动态规划(完全背包)或者数学方法。 重新梳理: 1. 完美回文数都是11的倍数(因为一个偶数位的只含0和1的回文数,其奇数位上的数字和等于偶数位上的数字和,所以能被11整除)。因此,若n不能被11整除,则无解(输出0?但题目要求最小个数,无解应该输出什么?题目没有明确,但通常输出-1或0?根据引用[3]和[4]的代码,它们输出0表示无解)。 2. 令m = n/11,则问题转化为:用最少的数字(每个数字是1, 91, 101)的和来表示m(因为11, 1001=11*91, 1111=11*101,所以原问题中每个完美回文数除以11后,得到1,91,101)。 3. 因此,问题变成:求m由1,91,101这三个数通过加法得到的最少个数(每个数可以用任意次)。 注意:为什么完美回文数可以只考虑11,1001,1111?因为其他完美回文数都可以由这些组合得到?实际上,更大的完美回文数(如100001,110011等)都可以写成这些数的线性组合(但可能不是非负整数组合?),然而,题目要求的是最少的个数,所以使用更大的数可能会减少个数?但是,我们注意到,更大的完美回文数(如100001)除以11后得到9091,而9091可以用91和101来表示(实际上,9091=91*99+101*2,这个不一定成立,需要验证)。因此,我们不一定只使用1,91,101,还需要考虑其他完美回文数? 但是,引用[3]和[4]都只用了三个数:11,1001,1111(即除以11后为1,91,101)。为什么?因为任何完美回文数除以11后得到的是一个形如a(由0和1组成的偶数位回文数除以11)的数,而这样的数可以用1,91,101来线性表示?这个结论不一定成立。 实际上,我们需要知道:是否所有的完美回文数都可以由11,1001,1111通过线性组合(系数为非负整数)得到?答案是否定的,比如10000001(除以11后是909091)就不一定能用91和101表示。所以,只使用这三个数可能无法凑出某些m。 那么,为什么题目中只使用这三个数呢?可能是因为题目中给定的n的范围较小(在引用[3]中,n最大为MAXN=100000),而在这样的范围内,使用1,91,101(三个数)已经足够表示所有的m(只要m大于等于某个值)。此外,由于1的存在,任何m(非负整数)都可以用1来凑(但个数为m,显然不是最优)。同时,91和101的使用可以降低个数。 但是,题目要求的是最少个数,所以我们需要考虑所有可能的完美回文数?那就会有无穷多个,无法处理。所以,我们可能需要一个更聪明的做法。 另一种思路:注意到完美回文数除以11后得到的数(记为k)也是一个正整数,并且k也是由0和1组成的偶数位回文数再除以11?不对,实际上,完美回文数可以写成:对于2k位数,其形式为:a_1 a_2 ... a_k a_k ... a_2 a_1,其中a_i为0或1,且a_1=1(无前导0)。那么这个数除以11的规则?我们知道,能被11整除的数的特性是:奇数位和与偶数位和的差能被11整除。而由于这个数是回文且偶数位,所以奇数位和等于偶数位和?不对,实际上,回文数的对称位置相同,但奇数位和偶数位分别是什么?例如4位数:位置1(千位)和位置4(个位)是奇数位,位置2(百位)和位置3(十位)是偶数位。那么奇数位和=位置1+位置4,偶数位和=位置2+位置3。而回文要求位置1=位置4,位置2=位置3。所以奇数位和=2*位置1,偶数位和=2*位置2。那么整个数的数字和=2*(位置1+位置2)。而奇数位和与偶数位和的差=2*(位置1-位置2),这个差要能被11整除。由于位置1和位置2都是0或1,且位置1是1(无前导0),所以位置1=1。因此差为2*(1-位置2)。当位置2=1时,差为0,能被11整除;当位置2=0时,差为2,不能被11整除。所以,只有当位置2=1时,这个数才能被11整除?不对,例如1001:位置1=1,位置2=0,位置3=0,位置4=1。奇数位(1和1)和为2,偶数位(0和0)和为0,差为2,不能被11整除?但1001/11=91,是可以整除的。所以上述推理错误。 实际上,我们重新检查:1001,奇数位(第1位和第3位?)实际上,在11的整除规则中,是从右往左数,第1位(个位)、第2位(十位)、第3位(百位)、第4位(千位)。奇数位:第1位(1)和第3位(0),和=1;偶数位:第2位(0)和第4位(1),和=1。差为0,所以能被11整除。 所以,对于偶数位(2k位)的数,从右往左,第1位(个位)、第2位(十位)...第2k位(最高位)。那么奇数位:第1,3,...,2k-1位;偶数位:第2,4,...,2k位。而回文性质告诉我们:第i位等于第2k+1-i位。那么,奇数位包括:第1位(等于第2k位)、第3位(等于第2k-2位)、...。所以,奇数位和等于所有奇数位上的数字之和,偶数位和等于所有偶数位上的数字之和。而整个数的数字和等于奇数位和+偶数位和。同时,由于回文,奇数位和实际上等于前k位中的奇数位(按从右往左数,实际上就是原数的后半部分?)这个关系比较复杂。 但我们已经知道,1001、11、1111都是完美回文数,且都能被11整除。因此,问题简化为:用11,1001,1111这三个数(以及其他的完美回文数?)来组合出n,要求个数最少。但是,由于完美回文数有无穷多个,我们需要考虑哪些呢? 参考数论中的“硬币问题”:一般用完全背包解决,但硬币种类很多时需要优化。但这里,我们可以证明:所有大于等于某个值的数都可以用91和101表示(因为91和101互质?不,gcd(91,101)=1,所以根据Frobenius硬币问题,最大不能表示的数为91*101-91-101=??)。所以,当m足够大时,我们可以用91和101来表示,而剩下的小部分用1。但是,这里我们要求最小个数,所以我们需要考虑使用1,91,101的组合来使得总个数最小。 因此,对于m,我们首先尽量多用较大的数(101,然后91,最后1)。但是,由于101和91不是倍数关系,我们不能简单地贪心(例如,尽量用101,然后用91,最后1),因为可能用91替换两个101会得到更优解(例如,202=101*2,而202=91*2+20,但20个1显然多于2?不对,202用两个101需要2个数,用两个91和20个1需要22个数,所以更差。因此贪心可能是正确的?)。 然而,实际上,由于91和101很接近,贪心可能不是最优。例如,m=182,贪心:182=101*1+81*1 -> 1+81=82个数?不对,实际上182=91*2,只需要2个数。所以我们需要动态规划。 但是,m的范围是多少?n最大100000,则m=n/11最大为100000/11≈9090。而动态规划的状态数就是m的大小(最大9090),然后物品有3个(1,91,101),那么三重循环(枚举物品和金额,再枚举个数)可能达到9090*3*(最多使用次数)?使用次数最多9090(对于1),这样会超时。所以需要优化。 优化:完全背包的标准优化。我们可以用一维数组和两层循环(外层物品,内层金额从小到大)来实现。 引用[3]的代码已经使用了类似完全背包的方法,但它没有使用标准的完全背包优化(内层循环顺序)。它的代码是: for i in 0..2: 复制上一层状态 然后枚举金额j从0到MAXN,再枚举使用当前物品的个数k(从0开始直到超过j) 这样的时间复杂度是O(MAXN * (MAXN/v[i]的最大值)),当v[i]=11时,最多使用次数约为100000/11≈9090,那么三重循环就是3 * 100000 * 9090,大约是27亿,超时。 所以,我们需要优化内层循环,用标准的完全背包优化:对于每种物品,内层循环从v[i]开始到MAXN,进行状态转移:f[j] = min(f[j], f[j-v[i]]+1) 引用[3]的代码没有这样优化,所以效率很低(但MAXN=100000,实际上只用了三个物品,且最大金额为100000,那么对于91和101,枚举次数会少很多?因为v[i]较大)。对于91和101,内层循环的次数会减少(91: 100000/91≈1099次循环,101:100000/101≈990次)。那么三层循环的总次数大约是: 对于11:100000 * (100000/11) ≈ 909000000 对于91:100000 * (100000/91) ≈ 109890000 对于101:100000 * (100000/101) ≈ 99000000 总计约:909000000+109890000+99000000=1,018,890,000 (10亿),在C语言中可能勉强在1秒内?但题目中MAXN=100000,而OJ平台可能无法承受10亿次循环。 所以,我们需要优化到O(MAXN*3)的复杂度。 标准完全背包的优化: f[i][j] = min(f[i-1][j], f[i][j-v[i]]+1) // 注意这里用同一层前面的状态 可以优化为一维数组:f[j] = min(f[j], f[j-v[i]]+1) 因此,引用[3]的代码可以改写为: f[0] = 0; for (int i = 0; i < 3; i++) { for (int j = v[i]; j <= MAXN; j++) { f[j] = min(f[j], f[j-v[i]]+1); } } 这样,复杂度就是O(MAXN*3),可以接受。 但为什么引用[3]的代码中,在输出时还有一个条件:n%11!=0?因为只有n是11的倍数,我们才能转换。而对于n%11!=0,输出0(表示无解?)。但是,题目要求的是最少个数,无解应该怎么表示?题目没有说,所以输出0可能表示需要0个?但0个数的和是0,而题目中n>=1,所以无解应该输出0?或者题目保证有解?但根据引用[1][2]的定义,显然不是所有的数都能表示(比如不能被11整除的数)。 因此,解题步骤: 1. 读入T个查询。 2. 对于每个查询n,如果n%11!=0,则输出0(表示无解?)。 3. 否则,令m = n/11。 4. 然后,问题变为:用1,91,101凑成m的最少个数(每个数可以用任意多次)。注意,这里要求个数最少。 5. 用完全背包(一维数组)计算dp[x]表示凑成x的最少个数,x从0到m_max(最大m_max=100000/11≈9090)。 6. 输出dp[m](如果dp[m]大于一个很大的值,则输出0?但题目保证有解吗?因为1的存在,所以m一定可以凑成,用m个1。因此dp[m]一定有解,无需判断无解。但引用[3]中却判断了无解,这是矛盾的?实际上,由于1的存在,任何m都可以表示,所以只要n%11==0,一定有解。所以引用[3]中的条件(n%11!=0)输出0,否则输出f[3][n](这里f[3][n]实际上就是dp[m]),但为什么还要判断dp[m]是否为INF?因为dp[m]可能没有被更新?不会,因为1的存在,所以dp[m]一定可以凑出来。因此,引用[3]中的条件可能是多余的(除了判断n%11==0)。所以,我们可以直接输出dp[m]。 但是,引用[3]的代码中,初始化f数组为INF,然后f[0][0]=0,然后转移。最后判断f[3][n]是否为INF,如果是则输出0。但由于1的存在,实际上不会出现INF(除非n大于MAXN,但题目保证n<=100000,而MAXN=100000,所以不会超范围)。所以这个判断也可以去掉。 因此,我们可以这样写代码: 预处理dp[0..maxm](maxm=1000000/11? 但题目n<=100000,所以maxm=100000/11≈9090,我们取maxm=9090+1000) 初始化dp[0]=0,其他为一个大数(比如0x3f3f3f3f) 硬币种类:coin[3] = {1, 91, 101} for (int i=0; i<3; i++) for (int j=coin[i]; j<=maxm; j++) dp[j] = min(dp[j], dp[j-coin[i]]+1); 然后对于每个查询: if (n%11) printf("0\n"); else printf("%d\n", dp[n/11]); 但是,为什么引用[3]的代码中硬币是1111,1001,11?因为它是用原数(完美回文数)除以11得到1,91,101,但实际背包时是对n/11做背包。所以代码中它直接对n(金额)进行背包,但物品是完美回文数(11,1001,1111)。这两种做法是等价的: 方案1:将n除以11,然后背包硬币为1,91,101(此时金额为n/11) 方案2:用完美回文数(11,1001,1111)直接背包,金额为n 但方案2中,物品的体积是11,1001,1111,那么dp[j]表示凑成金额j的最少个数。由于1111=11*101,1001=11*91,11=11*1,所以方案2中的背包问题实际上和方案1的背包问题本质相同。但是,方案2中,物品的最小体积是11,所以当j不是11的倍数时,无法转移?不对,因为我们可以用11来凑倍数。实际上,只有n是11的倍数时才有解,所以方案2中也只处理11的倍数?但方案2的背包过程需要处理所有金额(0到100000),其中非11的倍数位置应该保持INF。然后,对于查询n,如果n不是11的倍数,直接输出0(无解),否则输出dp[n]?但dp[n]是由11,1001,1111凑出来的最少个数。注意,dp[j]可能由其他完美回文数凑出吗?不,因为我们只用了三个物品。 所以,两种方案等价。 但是,方案2中,物品的体积较大(最小11),背包容量为100000,物品有三种:11,1001,1111。那么用完全背包的标准优化(一维,内层循环从小到大): for (int i=0; i<3; i++) for (int j=v[i]; j<=MAXN; j++) f[j] = min(f[j], f[j-v[i]]+1); 这样,复杂度为O(MAXN*3),可以接受(100000*3=300000)。 因此,引用[3]的代码可以优化为: f[0]=0; for (int i=0; i<3; i++) { for (int j=v[i]; j<=MAXN; j++) { f[j] = min(f[j], f[j-v[i]]+1); } } // 然后查询时,如果n%11!=0,输出0,否则输出f[n](这里注意:我们用三个物品(11,1001,1111)来凑n,如果f[n]还是INF,说明凑不成,但理论上不会,因为11的存在,所以n是11的倍数时,可以用n/11个11来凑,所以f[n]最大为n/11,不会为INF)所以可以去掉INF判断。 引用[4]的代码则用了一种枚举的方法,但时间复杂度高,且只枚举了三个硬币(1,91,101)的组合,并且不是求最小个数,而是求第一个可行解的个数,所以是错误的。 结论:在XTUOJ平台上,这道题的标准解法是动态规划(完全背包),预处理0到最大范围(100000)的解,然后查询时直接输出。 题目链接:由于用户没有给出具体题目链接,但根据引用[4]的代码开头注释“XTU OJ best回文数”,可能在XTUOJ上题目的名称是“best回文数”或者类似。 下面给出一道XTUOJ上可能的题目描述(模拟): 题目名称:完美回文数 题目编号:例如1234 题目描述: 一个十进制无前导0的整数a,如果a有偶数位,只含有0和1的数码,且从左到右和从右到左读都是一样的,那么我们称其为“完美回文数”。比如,1001,11这些都是完美回文数,而12,1010,1,101这些都不是“完美回文数”。 请问一个整数n等于最少多少个完美回文数的和? 输入: 第一行输入一个整数T (1<=T<=10000),表示测试数据组数。 接下来T行,每行一个整数n (1<=n<=100000)。 输出: 对于每组测试数据,输出一个整数,表示最少个数。如果无法表示,输出0。 样例输入: 3 11 22 1001 样例输出: 1 2 1 解题思路: 1. 由于完美回文数都是11的倍数,所以n不能被11整除时,直接输出0。 2. 当n能被11整除时,令m=n/11。问题转化为:用最少的1,91,101(分别对应完美回文数11,1001,1111)凑出m(每个数可以用任意多次)。注意,题目要求最少个数。 3. 使用完全背包求解。设dp[i]表示凑出i所需的最少个数,初始化dp[0]=0,dp[i]初始化为一个大数(比如0x3f3f3f3f)。 4. 硬币数组:coin[3] = {1,91,101}。 5. 对i从0到2,然后j从coin[i]到maxm(maxm取100000/11≈9090),更新dp[j]=min(dp[j], dp[j-coin[i]]+1)。 6. 对于每个n,输出dp[n/11]。 但是,这里有一个问题:是否存在某些m,使用更大的完美回文数(如100001等)会使得个数更少?理论上,可能存在。例如,如果我们有一个完美回文数是11111111(8个1),除以11=1010101,那么如果我们用这个数,可能比用1,91,101的组合个数更少。但是,题目要求的是最少的个数,所以我们需要考虑所有完美回文数吗? 然而,我们观察到,任何一个完美回文数除以11得到的数k,也是一个整数,而且k可以写成:k = (10^{2d-1}+1)/11 * a,其中a是一个由0和1组成的回文数?这样并不直观。但实际上,我们可以尝试证明:使用11,1001,1111这三个完美回文数,可以得到最优解。为什么? 因为任何其他的完美回文数都可以写成11*(100...01)的形式(例如100001=11*9091),而100...01又可以由1001和1111组合而成吗?并不显然。 所以,我们可能需要考虑所有的完美回文数。但完美回文数有无穷多个,怎么办? 注意:题目要求的是最少的个数。我们可以用数学归纳法证明:对于任意一个完美回文数(大于1111的),它总可以表示为若干个11,1001,1111的和?不一定成立。 因此,我们可能需要重新考虑:是否每个完美回文数都可以由11,1001,1111线性表示(非负整数系数)?答案是否定的。比如,完美回文数10000001(除以11后是909091),它可能无法用91,101线性表示。 那么,我们为什么引用[3]和[4]都只用了三个数?可能是因为题目给定的n范围较小(n<=100000),而在较大的完美回文数中,最小的完美回文数是11,1001,1111,然后下一个是100001(比100000大),所以对于n<=100000,只有11,1001,1111这三个完美回文数在这个范围内(因为下一个完美回文数100001已经大于100000了)。所以,题目中n<=100000,因此只能使用这三个完美回文数(因为其他的完美回文数都大于100000,不可能在求和时出现,因为题目要求的是和等于n,如果使用一个大于n的数,那是不可能的)。因此,完美回文数的候选集合只有11,1001,1111(因为下一个是100001>100000)。 所以,结论:对于n<=100000,完美回文数只有三个:11,1001,1111。因此,问题转化为用这三个数凑n(可重复使用),求最小个数。 那么,接下来就是标准的完全背包问题。 因此,我们可以在XTUOJ上提交以下代码(C语言): 预处理dp[0..100000](初始化为一个大数) dp[0]=0; int v[] = {11, 1001, 1111}; // 三个完美回文数 for (int i=0; i<3; i++) { for (int j=v[i]; j<=100000; j++) { if (dp[j-v[i]] != INF) { dp[j] = min(dp[j], dp[j-v[i]]+1); } } } 然后,对于每个查询: if (n%11 != 0) printf("0\n"); else { if (dp[n] >= INF) // 即凑不成,但实际上不会,因为11可以组成任何11的倍数,所以理论上dp[n]<=n/11 printf("0\n"); else printf("%d\n", dp[n]); } 但是,注意:1111比100000大吗?不,1111<100000,所以可以。 然而,我们还需要考虑:会不会有其他的不大于100000的完美回文数?例如: 4位数:1001,1111 6位数:100001(大于100000,不行),那么110011(110011>100000,不行),101101(101101>100000,不行)... 所以6位数都大于100000(因为最小的6位数是100001>100000)。 所以,只有两位数的11和四位数的1001,1111(1001是1001,1111是1111,都小于100000)是候选。 因此,候选就是这三个。 所以,XTUOJ上该题目的解题代码: #include <stdio.h> #include <string.h> #define MAXN 100000 #define INF 0x3f3f3f3f int dp[MAXN+10]; int min(int a, int b) { return a < b ? a : b; } int main() { int v[3] = {11, 1001, 1111}; memset(dp, INF, sizeof(dp)); dp[0] = 0; for (int i = 0; i < 3; i++) { for (int j = v[i]; j <= MAXN; j++) { if (dp[j - v[i]] != INF) { dp[j] = min(dp[j], dp[j - v[i]] + 1); } } } int T; scanf("%d", &T); while (T--) { int n; scanf("%d", &n); if (n % 11 != 0) { printf("0\n"); } else { if (dp[n] >= INF) { printf("0\n"); } else { printf("%d\n", dp[n]); } } } return 0; } 但是,这里有一个问题:11的倍数不一定只能用11,1001,1111来凑,理论上,我们知道11可以用11来凑,22可以用两个11,33用三个11,...,直到91可以用1001-11*? 不行,1001=1001,91=91*11?不对,91=91,而我们的dp是对n(不是n/11)进行的,所以22=11+11,dp[22]=2。 然而,22也可以不用两个11,有没有其他办法?没有,因为1001>22,1111>22。所以只能拆成11+11。 同理,1001=1001,所以dp[1001]=1。 但是,比如1221 = 1111+110,而110=11*10,所以需要11个1111?不对,1111+110=1111+11*10=1111+110=1221,需要1+10=11个数。但1221=11*111,所以也可以拆成111个11,需要111个数。那么最小个数应该是11吗? 不对,1221=1111+110,110可以用1001和11来凑?1001>110,所以110只能拆成11*10。所以最小个数是1+10=11。 但是,有没有更少的?1221=1111+110,而110无法用1001(因为1001>110),所以只能用11,所以10个11。总共11个。 另外,1221=1001*1+220,220=11*20,共1+20=21个。 或者,1221=1111+1001+(-891)?不行。 所以最小个数是11。 但是,如果我们不用1111,而只用11和1001,那么1221=1001+220=1001+11*20,共21个。 所以,使用1111可以优化到11个。 因此,以上代码应该能计算出dp[1221]=11。 测试:运行预处理,然后输出dp[1221]。 dp[0]=0 首先,用11:从11到100000,dp[11]=1, dp[22]=2, ..., dp[1221]=111(因为1221/11=111) 然后,用1001:从1001开始,dp[1001]=1,dp[1001+11]=dp[1012]=2,...,dp[1221]= min(111, dp[1221-1001]+1=dp[220]+1) dp[220] = 220/11=20,所以dp[1221]=min(111,21)=21 然后,用1111:从1111开始,dp[1111]=1,dp[1111+11]=dp[1122]=2,...,dp[1221]=min(21, dp[1221-1111]+1=dp[110]+1) dp[110]=110/11=10,所以dp[1221]=min(21,11)=11 所以,最终dp[1221]=11,正确。 因此,代码正确。 总结:XTUOJ完美回文数题目的解题思路和代码。 相关问题: 1. 除了11,1001,1111,还有哪些完美回文数小于10^5? 2. 如果n的范围更大(例如10^9),应该如何求解? 3. 完美回文数在整数序列中的分布密度如何? 由于用户要求生成相关问题,所以: §§相关问题§§ 1. 在完美回文数的题目中,为什么只需要考虑11,1001,1111这三个数? 2. 如果n的范围扩展到10^9,如何设计算法来解决完美回文数的最小个数问题? 3. 完美回文数还有哪些有趣的数学性质? </think>### XTUOJ平台完美回文数题目解析 #### 题目描述(参考引用[1][2]) - **完美回文数定义**:十进制无前导0的整数,满足: 1. 有偶数位数 2. 只含数码0和1 3. 是回文数(从左到右和从右到左读相同) - **示例**:$1001$、$11$ 是完美回文数;$12$、$1010$、$1$、$101$ 不是。 - **问题**:给定整数 $n$,求 $n$ 等于**最少多少个完美回文数的和**?若无法表示,输出0。 - **输入范围**:$1 \leq n \leq 100000$,$T$ 组测试数据。 #### 关键解题思路 1. **数学性质**: - 所有完美回文数都是 $11$ 的倍数(因其偶数位、回文、仅含0/1的性质)。 - **推论**:若 $n$ 不能被 $11$ 整除,则无解,输出 $0$。 2. **候选完美回文数的选择**: - 在 $n \leq 100000$ 范围内,只需考虑三个完美回文数: - $11$(最小偶数位回文) - $1001$(四位数回文) - $1111$(四位数回文) - 原因:更大的完美回文数(如六位数 $100001$)均 $>100000$,无法参与求和。 3. **问题转化**: - 设 $m = n/11$,问题转化为:用 $\{1, 91, 101\}$ 组合出 $m$ 的最少个数(因为 $11 \times 1 = 11$,$11 \times 91 = 1001$,$11 \times 101 = 1111$)。 - **目标**:求 $\min\left\{ k \mid k = a + b + c,\ a + 91b + 101c = m \right\}$,其中 $a, b, c \geq 0$。 4. **动态规划(完全背包)**: - **状态定义**:$dp[j]$ 表示凑出金额 $j$ 所需的最少完美回文数个数。 - **初始化**: - $dp[0] = 0$ - $dp[j] = \infty$(表示不可达) - **状态转移**(对每个候选数迭代): $$ dp[j] = \min(dp[j],\ dp[j - v_i] + 1) \quad \text{for} \ v_i \in \{11, 1001, 1111\} $$ - **复杂度**:$O(3 \times n)$,满足 $n \leq 100000$ 的要求。 #### 参考代码(优化版) ```c #include <stdio.h> #include <string.h> #define MAXN 100000 #define INF 0x3f3f3f3f int dp[MAXN + 10]; int coins[] = {11, 1001, 1111}; // 候选完美回文数 int min(int a, int b) { return a < b ? a : b; } int main() { memset(dp, INF, sizeof(dp)); dp[0] = 0; // 完全背包预处理 for (int i = 0; i < 3; i++) { for (int j = coins[i]; j <= MAXN; j++) { dp[j] = min(dp[j], dp[j - coins[i]] + 1); } } int T; scanf("%d", &T); while (T--) { int n; scanf("%d", &n); if (n % 11 != 0) { printf("0\n"); } else if (dp[n] >= INF) { // 理论上不会触发(因11可覆盖所有倍数) printf("0\n"); } else { printf("%d\n", dp[n]); } } return 0; } ``` #### 代码说明 1. **预处理**:对 $0$ 到 $100000$ 的所有整数,预先计算最少个数。 2. **查询处理**: - 若 $n \not\equiv 0 \pmod{11}$,输出 $0$(无解)。 - 否则输出 $dp[n]$(最小个数)。 3. **正确性保证**: - $11$ 可覆盖所有 $11$ 的倍数(如 $n = 11k$ 可用 $k$ 个 $11$ 凑出)。 - 加入 $1001$ 和 $1111$ 可优化某些情况(如 $n = 1221$ 时,$1111 + 10 \times 11 = 1221$ 仅需 $11$ 个)。 #### 示例计算 | $n$ | $n \mod 11$ | 分解方案 | 最少个数 | |----------|-------------|-----------------------|----------| | $11$ | $0$ | $11$ | $1$ | | $22$ | $0$ | $11 \times 2$ | $2$ | | $1001$ | $0$ | $1001$ | $1$ | | $1221$ | $0$ | $1111 + 11 \times 10$ | $11$ | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值