HDU7106 Function 题解

题目大意

定义 g ( x ) g(x) g(x)表示 x x x十进制下每一位数字之和,比如 g ( 123 ) = 1 + 2 + 3 = 6 g(123)=1+2+3=6 g(123)=1+2+3=6

f ( x ) = A x 2 g ( x ) + B x 2 + C x g 2 ( x ) + D x g ( x ) f(x)=Ax^2g(x)+Bx^2+Cxg^2(x)+Dxg(x) f(x)=Ax2g(x)+Bx2+Cxg2(x)+Dxg(x),在 [ 1 , n ] [1,n] [1,n]之间的最小值。( x x x为整数)。

其中, A , B , C , D , n A,B,C,D,n A,B,C,D,n为题目输入。( 1 ≤ n ≤ 1 0 6 1\le n \le 10^6 1n106)

思路

f ( x ) f(x) f(x)可以转化为 f ( x ) = [ A g ( x ) + B ] x 2 + [ C g 2 ( x ) + D g ( x ) ] x f(x)=[Ag(x)+B]x^2+[Cg^2(x)+Dg(x)]x f(x)=[Ag(x)+B]x2+[Cg2(x)+Dg(x)]x

不难发现, g ( x ) g(x) g(x)的值域是 [ 1 , n ] [1,n] [1,n]。而 g ( x ) g(x) g(x)的值一旦确定,那么 f ( x ) f(x) f(x)就成为了只能取特定自变量值的二次函数。

比如 g ( x ) = 10 g(x)=10 g(x)=10时, x x x可取 10 , 19 , 28... 10,19,28... 10,19,28...等。因为此时 f ( x ) f(x) f(x)已经是二次函数了,那么它的最小值,显然为

  • 开口向下,两端边界值。
  • 开口向上,对称轴两侧的那两个值。

所以我们对于 g ( x ) g(x) g(x)的每一个值,计算它的最小值,最后再比较即可。

具体做法

  1. 先与处理出 v v v数组, v [ i ] [ j ] v[i][j] v[i][j]表示,在满足 g ( x ) = i g(x)=i g(x)=i的所有 x x x中,从小到大的第 j + 1 j+1 j+1个是多少。比如 v [ 2 ] [ 1 ] v[2][1] v[2][1]显然是11(因为第一个是2)。
  2. 枚举 g ( x ) g(x) g(x) 1 1 1 54 54 54的值,计算对称轴。计算对称轴两侧的值。(如果开口向下,那么它的最小值显然就是两侧,那么就u不需要计算对称轴两侧的值了)

代码

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

const int maxN = 10010;
typedef long long ll;

vector<ll> v[maxN];
ll a, b, c, d, n, T;

ll calcG(ll x)
{
    ll ans = 0, tmp = x;
    while(tmp) {
        ans += tmp % 10;
        tmp /= 10;
    }
    return ans;
}

ll calc(ll x)
{
    ll g = calcG(x);
    return a * x * x * g + b * x * x + c * x * g * g + d * g * x;
}

inline void init()
{
    for(int i = 1; i <= 1000000; ++i) {
        v[calcG(i)].push_back(i);
    }
}

int main()
{
    init();
    scanf("%d", &T);
    while(T--) {
        scanf("%lld%lld%lld%lld%lld", &a, &b, &c, &d, &n);
        ll ans = calc(1);
        for(int i = 1; i <= 54; ++i) {
            ll A = a * i + b;
            if(A == 0)
                continue;
            ll B = c * i * i + d * i;
            ll mid = (-B) / (A + A); //计算对称轴
            if(mid <= 0)
                continue;
            ll tmp = upper_bound(v[i].begin(), v[i].end(), mid) - v[i].begin();
            ll bound = upper_bound(v[i].begin(), v[i].end(), n) - v[i].begin();
            if(tmp < bound && tmp >= 0 && v[i][tmp] <= n) ans = min(calc(v[i][tmp]), ans);
            if(tmp - 1 <bound && tmp - 1 >= 0 && v[i][tmp - 1] <= n) ans = min(calc(v[i][tmp - 1]), ans);// 计算对称轴两侧的值
        }
        for(int i = 1; i <= 54; ++i) {
            ll x1 = v[i][0];
            if(x1 > n)
                break;
            ll x2 = v[i][upper_bound(v[i].begin(), v[i].end(), n) - v[i].begin() - 1];
            ans = min(ans, min(calc(x1), calc(x2)));
        }
        printf("%lld\n", ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值