原式:
f(x)=A(x^2)*g(x)+Bx^2+C*x*(g(x)^2)+D*x*g(x)
经过变换后
f(x)=(A*g(x)+B)*x^2+(C*(g(x)^2+D*g(x))x
假设,我们把g(x)看作一个常数
那么原式就会变成Nx^2+Mx
就成了一个一元二次方程
但是,g(x)是会随着x的变化而变化的
但值得注意的是:由于1<=x<=1e6
所以1<=g(x)<=54 (g(99999)=54)
所以我们可以把x根据其g(x)的值分成54个互相独立的部分,每个部分g(x)是相同的
我们可以用这个代码来将这些数分成54份:
int get_g(int x, int c = 0) {
for (; x; x /= 10)
c += x % 10;
return c;
}
for (int i = 1; i <= 1e6; ++i)
g[get_g(i)].push_back(i);
当g(x)被固定住后,就可以根据一元二次函数的性质迅速找到其极小值
我们设化简后的函数为Ax^2+Bx
(其中A=(A*g(x)+B),B=C*(g(x)^2+D*g(x))
当A<0时,该函数为开口向上的函数,其最小值在数轴两段(g[i][0],g[i][r]
)
当A=0时,该函数为线性函数,其最小值依旧是数轴两段中的一个
当A>0时,开口向上,我们可以用三分的方法找对称轴,在三分过程中就完成对ans最小值的更新
代码:
#include <bits/stdc++.h>
#define int long long
using namespace std;
int get_g(int x, int c = 0) {//g函数
for (; x; x /= 10)
c += x % 10;
return c;
}
int f(int a, int b, int x) {
return a * x * x + b * x;
}
vector<int> g[65];
void solve() {
int a, b, c, d, n, ans = 1e18;
cin >> a >> b >> c >> d >> n;
for (int i = 1; i <= 60; ++i)
if (g[i].size()) {
int l = 0;
int r = upper_bound(g[i].begin(), g[i].end(), n) - g[i].begin() - 1;
//找到边界
if (l > r)
continue;
//Ax^2+Bx=0;
int A = a * i + b;
int B = c * i * i + d * i;
if (A <= 0)//该数是一个开口向下的函数
ans = min({ ans, f(A, B, g[i][0]), f(A, B, g[i][r]) });
else {
while (l <= r) {//三分对称轴
int m1 = l + (r - l) / 3;
int m2 = r - (r - l) / 3;
int r1 = f(A, B, g[i][m1]), r2 = f(A, B, g[i][m2]);
if (r1 > r2)
l = m1 + 1;
else
r = m2 - 1;
ans = min({ ans, r1, r2 });
}
}
}
cout << ans << endl;
}
signed main() {
for (int i = 1; i <= 1e6; ++i)
g[get_g(i)].push_back(i);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}