Palindrome Number

判断回文数字
本文介绍了一种不使用额外空间判断整数是否为回文数的方法。通过比较数字的高位和低位来实现,适用于Python编程环境。

地址:点击打开链接

题目要求判断是否回文数字,而且要求不能用额外空间,意味着将数字转字符串不行,将数字颠倒也不行,那只有判断高位和地位数字是否相同了,而且判断条件要注意


答案:

class Solution(object):
    def isPalindrome(self, x):
        """
        :type x: int
        :rtype: bool
        """
        if x <= 9 and  x >= 0:
            return True
        if x < 0:
            return False
        low = 0
        high = 0
        base = 10
        while x / base >= 10:
            base *= 10
        while x != 0:
            low = x % 10
            high = x / base
            if low != high:
                return False
            x %= base
            x /= 10
            base /= 100
        return True


<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$ | ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值