2021ccpc网络赛06(hdu7106) Function(数学+三分)

原式:

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;
}

猜你喜欢

转载自blog.csdn.net/fdxgcw/article/details/119984743