题解:P17292 [Algo Beat Contest 013 & MSOI R2] 告诉我

· · 题解

题意简述

有两个初值为 0 的计数器。每消耗一点能量,两个计数器同时加一;第一个计数器每到达 a 的倍数便贡献一级。第二个计数器不小于 c 时,可以额外贡献一级,并将两个计数器清零。

求消耗 x 点能量最多能获得多少级。

解题思路

每次触发高级保底,两个计数器都会清零。因此,任意策略都能唯一划分为若干个完整段和最后的剩余段。完整段长度至少为 c,并额外贡献一级;剩余段不触发高级保底。

长度为 l 的完整段贡献 \lfloor l/a\rfloor+1 级,剩余段贡献 \lfloor l/a\rfloor 级。

c=pa+r,其中 0\le r<a

固定高级保底触发 k 次。先把每个完整段设为最短长度 c。这样消耗 kc 点能量,并获得 k(p+1) 级。尚未分配的能量为:

e=x-kc

r=0,所有完整段恰好停在常规保底边界。此后无论把能量分配给哪个段,每增加一个常规等级都需要 a 点能量。因此:

A(k)=k(p+1)+\left\lfloor\frac{e}{a}\right\rfloor

此时每增加一次高级触发都会多得一级,故取 k=\lfloor x/c\rfloor 最优。

下面设 r>0,并记 b=a-r。每个最短完整段已经积累了余数 r。向该段再分配 b 点能量,就能获得一个常规等级。每个完整段恰有一次这种低价补全机会,共有 k 次。

用完这些机会后,任何额外常规等级都需要完整的 a 点能量。交换分配顺序不会改变可行性,所以最优策略一定先使用低价补全。

因此应先完成尽可能多的低价补全。令:

t=\min\left(k,\left\lfloor\frac{e}{b}\right\rfloor\right)

固定 k 时的最优答案为:

A(k)=k(p+1)+t+\left\lfloor\frac{e-tb}{a}\right\rfloor

该式既是上界,也能通过先补全 t 个完整段达到。因此,它就是固定触发次数后的最优值。

d=c+b=(p+1)a。若 k\le\lfloor x/d\rfloor,剩余能量足够补全每个完整段。代入上式并合并剩余的整组 a 可得:

A(k)=\left\lfloor\frac{x}{a}\right\rfloor+k

所以前一段关于 k 单调递增,其最优点为 \lfloor x/d\rfloor

k>\lfloor x/d\rfloor,就无法完成全部低价补全。此时 t=\lfloor e/b\rfloor<k,补全后的余量又小于 b\le a。因此:

A(k)=k(p+1)+\left\lfloor\frac{x-kc}{b}\right\rfloor

相邻两项之差可以写为:

A(k+1)-A(k)=p+1-\delta_k

其中 \delta_k 只可能是 \lfloor c/b\rfloor\lceil c/b\rceil。比较 (p+1)bc:前者较小时,所有相邻差均非正;前者较大时,所有相邻差均非负;相等时,所有相邻差均为零。故后一段也单调,最优点只能在端点。

综上,只需计算以下至多四个触发次数:

0,\left\lfloor\frac{x}{d}\right\rfloor,\left\lfloor\frac{x}{d}\right\rfloor+1,\left\lfloor\frac{x}{c}\right\rfloor

其中第三个数只有不超过 \left\lfloor\frac{x}{c}\right\rfloor 时才合法。算法只计算常数个候选点,时间复杂度为 O(1),空间复杂度为 O(1)

参考代码

#include <bits/stdc++.h>
using namespace std;

using ll=long long;
ll get(ll a,ll c,ll x,ll k)
{
    ll p=c/a,r=c%a,e=x-k*c;
    ll ans=k*(p+1);
    if(!r)return ans+e/a;
    ll b=a-r,t=min(k,e/b);
    return ans+t+(e-t*b)/a;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    ll a,c,x;
    cin>>a>>c>>x;
    ll d=(c/a+(c%a!=0))*a;
    array<ll,4> q={0,x/d,x/d+1,x/c};
    ll ans=0;
    for(auto k:q)if(k<=x/c)ans=max(ans,get(a,c,x,k));
    cout<<ans<<'\n';
    return 0;
}