Project Euler 269

Project Euler 269

题目

Polynomials with at least one integer root

A root or zero of a polynomial $P(x)$ is a solution to the equation $P(x) = 0$.

Define $P_n$ as the polynomial whose coefficients are the digits of n.

For example, $P_{5703}(x) = 5x^3 + 7x^2 + 3$.
We can see that:

  • $P_n(0)$ is the last digit of $n$,
  • $P_n(1)$ is the sum of the digits of $n$,
  • $P_n(10)$ is $n$ itself.

Define $Z(k)$ as the number of positive integers, $n$, not exceeding $k$ for which the polynomial $P_n$ has at least one integer root.

It can be verified that $Z(100 000)$ is $14696$.

What is $Z(10^{16})$?

解决方案

令$N=16$。不难发现,方程$P_n(x)=0$的整数根只能在范围$[-9,0]$内,因为值$P_n(-10)$的最高次数项的绝对值一定大于其余项的绝对值之和。

如果$x=0$是方程$P_n(x)=0$的解,当且仅当$n$是$10$的倍数。这类数一共有$10^{N-1}-1+1$个,注意这里其中一个答案是$10^N$。

接下来只考虑范围$[-9,-1]$内的解情况。由于$P_n(x)$的常系数项最多为$9$,而多项式$(x+1)(x+2)(x+3)(x+4)$的常数项已经达到了$24$,因此$P_n(x)$最多只有$3$个整数根。

考虑使用容斥原理,先计算出有多少个多项式的解含有$-1,-2,\dots,-9$,再秋求和,然后再计算有多少个多项式的解同时含有$(-1,-2),(-1,-3),\dots$,并一一减去,相应的计算三个解时的情况,并加上。

考虑一个$m$位有前导$0$数对应的多项式$pm(x)$,它可以写成$p_m(x)=x\cdot p{m-1}(x) + dm$。其中$d_m$表示常数项,$0\le d_m\le 9$,注意,$d_m$也表示这个$m$位有前导$0$数的个位。那么可以写成$p{m-1}(x)=\dfrac{p_m(x)-d_m}{x}$,进而递归寻找更小的项。

令状态$f3(n,b_1,b_2,b_3)(0\le n< N)$表示有多少个多项式$p{N-n-1}(x)$,满足$p_{N-n-1}(a_k)=b_k,k=1,2,3$。那么可以写出$f$的状态转移方程:

其中,$[]$表示示性函数,表示$[]$里面的值是否为真,如果为真,那么值为$1$,否则值为$0$.

方程的最后一行,表示$d_m$随意从$0\sim 9$中随意选择。由于我们一开始已经计算了个位为$0$的情况,因此个位不能选择$0$。

一开始,$p_{N-1}(x)=0$,因此同时具有$a_1,a_2,a_3$三个根的多项式个数为$f_3(0,0,0,0)$。

$f_2,f_1$的计算过程也类似。

最终用容斥原理将它们整合在一起即可。实际上,$|b_1|,|b_2|,|b_3|$这几个值都比较小。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 16;
const int O = 100;
int a1, a2, a3;
ll f[N+1][O+O];
ll g[N+1][O+O][O+O];
ll h[N+1][O+O][O+O][O+O];

ll f1(int n, int b) {
ll &ans = f[n][b + O];
if (ans != -1) return ans;
if (n == N - 1) {
if (b >= 0 && b <= 9) return ans = 1;
else return ans = 0;
}
ans = 0;
for (int m = (n == 0 ? 1 : 0); m < 10; m++) {
if ((b - m) % a1 != 0) continue;
ans += f1(n + 1, (b - m) / a1);
}
return ans;
}
ll f2(int n, int b1, int b2) {
ll &ans = g[n][b1 + O][b2 + O];
if (ans != -1) return ans;
if (n == N - 1) {
if (b1 >= 0 && b1 <= 9 && b1 == b2) return ans = 1;
else return ans = 0;
}
ans = 0;
for (int m = (n == 0 ? 1 : 0); m < 10; m++) {
if ((b1 - m) % a1 != 0 || (b2 - m) % a2 != 0) continue;
ans += f2(n + 1, (b1 - m) / a1, (b2 - m) / a2);
}
return ans;
}
ll f3(int n, int b1, int b2, int b3) {
ll &ans = h[n][b1 + O][b2 + O][b3 + O];
if (ans != -1) return ans;
if (n == N - 1) {
if (b1 >= 0 && b1 <= 9 && b1 == b2 && b2 == b3) return ans = 1;
else return ans = 0;
}
ans = 0;
for (int m = (n == 0 ? 1 : 0); m < 10; m++) {
if ((b1 - m) % a1 != 0 || (b2 - m) % a2 != 0 || (b3 - m) % a3 != 0) continue;
ans += f3(n + 1, (b1 - m) / a1, (b2 - m) / a2, (b3 - m) / a3);
}
return ans;
}
int main() {
ll ans = 1;
for (int i = 1; i < N; i++)
ans *= 10;
for (int i = 1; i < 10; i++) {
a1 = -i;
memset(f, -1, sizeof(f));
ans += f1(0, 0);
}
for (int i = 1; i < 10; i++)
for (int j = i + 1; i * j < 10; j++) {
a1 = -i;
a2 = -j;
memset(g, -1, sizeof(g));
ans -= f2(0, 0, 0);
}
for (int i = 1; i < 10; i++)
for (int j = i + 1; i * j < 10; j++)
for (int k = j + 1; i * j * k < 10; k++) {
a1 = -i;
a2 = -j;
a3 = -k;
memset(h, -1, sizeof(h));
ans += f3(0, 0, 0, 0);
}
printf("%lld\n", ans);
}