Acwing第89场周赛——加减乘(4-4)

77 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 4 天,点击查看活动详情

4805. 加减乘(免费报名周赛后可查看)

题目描述

规定两种数字操作:

  • 加减操作,将数字加 11 或减 11,代价为 xx
  • 乘法操作,将数字乘 22,代价为 yy

每种操作的使用次数不限。

请你计算,通过上述操作,将 00 变为 nn,所需花费的最小代价。

输入共一行,包含三个整数 n,x,yn,x,y

输出一个整数,表示最小代价。

前 55 个测试点满足 1n1001\le n \le 100
所有测试点满足 1n1071\le n \le 10^71x,y1091\le x,y \le 10^9

input1

8 1 1

output1

4

input2

8 1 10

output2

8

题目分析

这是一道 DP 的题目。

定义数组 f[i]f[i] 表示从 00ii 的最小花费。

可以证明一种最优解方式为以下情况。

如果 ii 是偶数,则 f[i]f[i] 可以从 f[i1]f[i-1]f[i/2]f[i/2] 中较小值转移而来,可以证明不会从比 ii 大的数 jj 转移而来,代价分别为 xxyy

如果 ii 是奇数,则 f[i]f[i] 可以从 f[i1]f[i-1]f[i+1]f[i+1] 中较小值转移而来,f[i+1]f[i+1] 则从 f[(i+1)/2]f[(i+1)/2] 转移而来,代价分别为 xxx+yx+y

注意 x,yx,y 可以取到 1e91e9f[i]f[i] 会有爆 int\text{int} 的可能,且需要计算题目所给的空间是否满足。

时间复杂度为 O(n)O(n)

Accpet代码(DP)

#include <bits/stdc++.h>

using namespace std;
using ll = long long;

const int N = 1e7 + 10;
ll f[N];

int main()
{
    int n, x, y;
    cin >> n >> x >> y;
    
    memset(f, 0x3f, sizeof f);      // 0x3f3f3f为当前类型的足够大(非最大)的常量
    f[0] = 0;
    for (int i = 1; i <= n; i ++)
    {
        if (i % 2)
            f[i] = min(f[i - 1] + x, f[(i + 1) / 2] + y + x);
        else
            f[i] = min(f[i - 1] + x, f[i / 2] + y);
    }
    cout << f[n];
    return 0;
}

另一种牺牲空间时间代码较简洁的方法
本题 f[1] 的答案是显然的,以此为基本可以由两种方式的最小值求得 f[2],依次递归,最终求得答案,可以证明其的不漏性。

#include <bits/stdc++.h>
#define int long long

using namespace std;

const int N = 2e7 + 10;

int n, x, y, f[N];

signed main()
{
    cin >> n >> x >> y;
    memset(f, 0x3f, sizeof f);
    f[0] = 0;
    for (int i = 1; i <= n; i++)
    {
        f[i] = min(f[i], f[i - 1] + x);
        f[i] = min(f[i], f[i + 1] + x);
        f[i * 2] = f[i] + y;
    }

    cout << f[n];
    return 0;
}