以下回答由chatgpt基于相关博客总结生成:
1
问题描述
我需要计算给定正整数N(大于5小于501),将其分解成3个正整数之和的可能方案数。其中,要求这3个正整数各不相同,且它们不能包含数字3和7。请问共有多少种符合要求的分解方法?(例如,对于N为8,可分解为1,1,6、1,2,5、1,3,4、2,2,4、2,3,3等共5种,其中满足要求的分解方法只有1种:1,2,5。)
解答
思路
我们考虑使用三重循环分别枚举每一个数字,然后统计符合条件的和的数量。
比如我们尝试枚举 $x+y+z=7$ 的情况:
for x in range(1, 8):
for y in range(x + 1, 8):
for z in range(y + 1, 8):
if x != y and y != z and x != z:
print(x, y, z)
这样,对于每一个 $x+y+z=n$ 的情况都会使用上述三重循环计算出所有的答案。
但是这样复杂度过高,我们需要优化。那么该怎么办呢?我们可以把思路反一下:即,对于每一个 $x+y=a$ 的情况,统计出 a 有多少种情况满足 $x+y=a$,然后再枚举 $a+z=n$ 的情况,利用前面的结果即可计算出答案。
我们首先假设它非常大,统计出每一个 $x+y=i$ 的答案:
# origin 表示未去掉 3 和 7 的方案数,after 表示已去掉 3 和 7 的方案数
origin_ans = [-1 for i in range(N + 1)]
after_ans = [-1 for i in range(N + 1)]
for i in range(1, N + 1):
cnt = 0
for x in range(1, i):
y = i - x
cnt += min(x, y - 1) # 注意指定边界条件,avoid 3 and 7
origin_ans[i] = cnt
after_ans[i] = cnt - (1 if i not in [3, 7] else 0)
这样,我们就计算出了 $1$ 到 $N$ 之间去掉 3 和 7 的拆分方案数目了。接下来,我们再枚举 $a + z = n$ 这个等式,统计满足条件的方案数即可。
ans = 0
for a in range(1, n): # 枚举所有的 a
z = n - a
if z < 2 * a: # 防重复
break
ans += after_ans[a] * after_ans[z] # 乘法原理计算需要的答案
print(ans)
至此,我们就完成了问题的求解过程。
代码
N = 501
# origin 表示未去掉 3 和 7 的方案数,after 表示已去掉 3 和 7 的方案数
origin_ans = [-1 for i in range(N + 1)]
after_ans = [-1 for i in range(N + 1)]
for i in range(1, N + 1):
cnt = 0
for x in range(1, i):
y = i - x
cnt += min(x, y - 1) # 注意指定边界条件,avoid 3 and 7
origin_ans[i] = cnt
after_ans[i] = cnt - (1 if i not in [3, 7] else 0)
def solve(n):
ans = 0
for a in range(1, n): # 枚举所有的 a
z = n - a
if z < 2 * a: # 防重复
break
ans += after_ans[a] * after_ans[z] # 乘法原理计算需要的答案
return ans
print(solve(8)) # expect 1
2
问题描述
输入正整数n,判断从1到n之中,数字1一共要出现几次。例如1123这个数,则出现了两次1。例如15,那么从1到15之中,一共出现了8个1。
解答
思路
对于这道题,我们不能直接枚举每一个数中的 1 的数量,因为对于 $n$ 的范围 [1, 30000],数据规模过大,枚举的时间复杂度无法接受。
那么,如何优化呢?我们考虑统计出 $1, 2, 3, \cdots, 9, 11, 12, \cdots, 19, 21, 22$ 中出现了多少个 $1$, $10, 20, \cdots, 90$ 中出现了多少个 $1$。比如,对于 $1$ 至 $9$ 的数,显然只有单个的数(1, 2, $\cdots$, 9)中才会出现 1,所以这些数字中一共会出现 $9$ 次 1。而对于 1 至 19 的数,它们中一共会出现 $11$ 次 1(除去 11 外,其余的都会出现一个 1;而在十位为 1 的数字中,又会出现 10 次 1)。那么这样的做法是否可行呢?
答案是可以的,我们统计 1 至 9,1 至 99,1 至 999 中一共出现了多少个 1,然后根据 $n$ 所在的区间来计算答案即可。
代码
def c10(n): # 计算 n 之前的数字中统计到的次数
return n // 10 + (1 if (n % 10) >= 1 else 0)
# calc 1 number from 1 to 9, 1 to 99, 1 to 999
sum1 = 0
for i in range(1, 10):
sum1 += c10(3000 + i * 100 - 1) - c10(i * 100 - 1) + (i * 100)
ans = sum1 // 10
print(ans)
注意,这里我们采用了比较巧妙的方式,先计算出所有的结果(用变量 sum1
记录),最后再做一次除法。
比如,对于数字 1,它一共统计到了 301 次(出现了 10 次 1),类似的,对于数字 2,它一共统计到了 302 次(20 次 1)。这样计算出来的答案肯定是不正确的,但我们已经计算出了 1-9,1-99,1-999 范围内的所有结果,所以我们只需要将范围缩小到 $1001$ 至 $n$ 即可,计算答案时,再除以 10 即可。
def solve(n):
sum1 = 0
for i in range(1, 10):
sum1 += c10(1000 + i * 100 - 1) - c10(i * 100 - 1) + (i * 100)
return (sum1 + c10(n) - 1) // 10 + (1 if n % 10 >= 1 else 0)
print(solve(15)) # expect 8