Codeforces Round 1043 (Div. 3)

C2

The Cunning Seller (hard version)

第一段:把 nnn 拆成 3 的幂和(相当于三进制)

复制代码

for(int i=0;n>0;i++) { while(n%3) { n-=1; mn+=1; use[i]++; } n/=3; }

这段等价于把 nnn 写成三进制:

n=∑di⋅3in=\sum d_i \cdot 3^in=∑di​⋅3i,其中 di∈{0,1,2}d_i\in\{0,1,2\}di​∈{0,1,2}。

  • 对第 i 位:n%3 是这一位的值(1 或 2)。

  • while(n%3){ n--; use[i]++; mn++; }:把这位的余数"扣掉"并记在 use[i],相当于用了 d_i 次"买 3i3^i3i"的交易。这样处理完后该位变成 0。

  • 然后 n/=3,进入更高一位。

因此循环结束后:

  • use[i] = d_i(三进制每一位就是用了多少个 3i3^i3i 的交易);

  • mn = sum d_i能凑出 n 的最少交易数

可行性判定

复制代码

if(mn>k){ cout<<-1; return; }

若最少交易数都超过 k,必然不可能,直接 -1。


第二段:计算还能"拆分"的预算

复制代码

ll t=(k-mn)/2;

一次拆分指:把 1 个"买 3i3^i3i"的交易,换成 3 个"买 3i−13^{i-1}3i−1"的交易:

  • 交易数从 1 变 3,+2 次交易

  • 买到的西瓜总数不变(仍是 3i3^i3i);

  • 费用会下降(见下节)。

由于每次拆分会额外消耗 2 次交易额度,因此最多可拆分次数是

t=⌊k−mn2⌋.t=\left\lfloor \frac{k-mn}{2}\right\rfloor.t=⌊2k−mn​⌋.


第三段:从高位到低位贪心拆分(为什么这样最省钱)

复制代码

for(int i=use.size()-1;i>0;i--){ ll b=min(t,use[i]); // 在第 i 位最多拆 b 次 use[i]-=b; use[i-1]+=3*b; t-=b; // 消耗 b 次"拆分配额" }

为什么拆分会降费?

设"买 3i3^i3i"的费用为 cic_ici​。题意给的价格化成通用式是

ci={3,i=03 i+1+i⋅3 i−1,i≥1c_i= \begin{cases} 3, & i=0\\ 3^{\,i+1}+i\cdot 3^{\,i-1}, & i\ge 1 \end{cases}ci​={3,3i+1+i⋅3i−1,​i=0i≥1​

把 1 个 3i3^i3i 拆成 3 个 3i−13^{i-1}3i−1 的费用差:

ci−3ci−1=(3i+1+i3i−1)−3(3i+(i−1)3i−2)=3i−1  >  0.c_i - 3c_{i-1} = \big(3^{i+1}+i3^{i-1}\big) - 3\big(3^{i}+(i-1)3^{i-2}\big) = 3^{i-1} \;>\; 0.ci​−3ci−1​=(3i+1+i3i−1)−3(3i+(i−1)3i−2)=3i−1>0.

也就是说:单买 1 个 3i3^i3i 比买 3 个 3i−13^{i-1}3i−1 贵 3i−13^{i-1}3i−1 金币。所以拆分一次就能省下 3i−13^{i-1}3i−1 金币。

为什么"从高到低拆"是最优?

一次拆分能省的钱是 3i−13^{i-1}3i−1,随 iii 单调增。

在"可拆分次数 t 固定"的前提下,要最大化总节省,自然应该优先用在最大的 iii 上(贪心正确性可用交换论证:若存在最佳解中先拆小位,改成先拆高位不会变差)。

于是代码从 i=最高位 往下,把能拆的都拆(受限于 t 和 use[i] 的个数)。


第四段:按最终方案计算最小费用

复制代码

ll res=0, x=1; // x = 3^i for(int i=0;i<20;i++) { ll c = 3*x + i*(x/3); // c_i res += use[i]*c; x *= 3; } cout<<res<<endl;

这里 x = 3^i,于是

ci=3⋅3i+i⋅3i−1=3 i+1+i⋅3 i−1.c_i = 3\cdot 3^i + i\cdot 3^{i-1} = 3^{\,i+1} + i\cdot 3^{\,i-1}.ci​=3⋅3i+i⋅3i−1=3i+1+i⋅3i−1.

当 i=0i=0i=0 时,x/3=0,得到 c0=3c_0=3c0​=3。

所以这一式子统一兼容 i=0i=0i=0 与 i≥1i\ge1i≥1 情况。

把每一位的数量 use[i] 乘以对应费用 c_i 累加,就是最小总费用。


小例子快速过一遍

  • n=3, k=3

    三进制 10use[1]=1, mn=1
    t=(3-1)/2=1

    i=1 拆 1 次:use[1]=0, use[0]+=3

    费用 = 3 * 3 = 9(比直接用一次 313^131 的 10 更省)。

  • n=8, k=3

    三进制 22mn=4 > k,输出 -1

  • n=9, k=1

    三进制 100mn=1t=0,不能拆;

    费用 = c2=3⋅9+2⋅3=33c_2 = 3\cdot 9 + 2\cdot 3 = 33c2​=3⋅9+2⋅3=33。


复杂度与边界

  • 三进制长度 ≤20\le 20≤20(对 n≤109n\le 10^9n≤109),每段都是 O(位数);每组数据 O(20)O(20)O(20)。

  • 费用与中间量均在 64 位整数范围内安全。

  • 代码里 use 开 30,而最后费用循环到 20,n\le 1e9 实际最高位 ≤19\le 19≤19,所以也安全;更工整可以统一都用 20。

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    void solve()
    {
    ll n,k;
    cin>>n>>k;
    ll re=n,mn=0;
    vector<ll> use(30);
    for(int i=0;n>0;i++)
    {
    while(n%3)
    {
    n-=1;
    mn+=1;
    use[i]++;
    }
    n/=3;
    }
    n=re;
    if(mn>k)
    {
    cout<<-1<<endl;
    return ;
    }
    ll t=(k-mn)/2;
    for(int i=use.size()-1;i>0;i--)
    {
    ll b=min(t,use[i]);
    use[i]-=b;
    use[i-1]+=3b;
    t-=b;
    }
    ll res=0,x=1;
    for(int i=0;i<20;i++)
    {
    ll c=3
    x+i*(x/3);
    res+=use[i]c;
    x
    =3;
    }
    cout<<res<<endl;
    }
    int main()
    {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int T=1;
    cin>>T;
    while(T--)
    solve();
    return 0;
    }

相关推荐
flashlight_hi1 小时前
LeetCode 分类刷题:2529. 正整数和负整数的最大计数
python·算法·leetcode
花火|1 小时前
算法训练营day60 图论⑩ Bellman_ford 队列优化算法、判断负权回路、单源有限最短路
算法·图论
2501_924890521 小时前
商超场景徘徊识别误报率↓79%!陌讯多模态时序融合算法落地优化
java·大数据·人工智能·深度学习·算法·目标检测·计算机视觉
艾醒2 小时前
大模型面试题剖析:模型微调和蒸馏核心技术拆解与考点梳理
算法
艾醒2 小时前
大模型面试题剖析:微调与 RAG 技术的选用逻辑
算法
NAGNIP3 小时前
一文弄懂MOE
算法
NAGNIP3 小时前
一文搞懂微调技术的发展与演进
算法
古译汉书3 小时前
蓝桥杯算法之基础知识(2)——Python赛道
数据结构·python·算法·蓝桥杯
地平线开发者3 小时前
LLM 中增量解码与模型推理解读
算法·自动驾驶
.Vcoistnt4 小时前
Codeforces Round 1043 (Div. 3)(A-E)
数据结构·算法