P1049 · OFFICIAL SOLUTION

P1049 糖果店 官方题解

Gioush OJ · P1049 糖果店

糖果店

【题意简述】

nn 颗糖果,第 ii 颗糖果的价格为 aia_i。至多使用 kk 个礼盒,每个礼盒装入两颗糖果,并只支付其中较贵者的价格。给定预算 mm,求最多能够购买多少颗糖果。

【数据点 1∼31\sim 31∼3】

【题目描述】

这一档保证 n10n\leq 10

【Hint】

每颗糖果有三种状态:不购买、单独结算、装入礼盒。枚举后检查礼盒是否能够两两配对。

【解法】

枚举购买集合,再枚举其中哪些糖果进入礼盒。对于一颗尚未结算的糖果,可以单独购买,也可以选择另一颗尚未结算的糖果与它组成礼盒。

检查总费用是否不超过 mm,取所有合法方案中购买数量的最大值。时间复杂度可以做到 O(3nn)\mathcal{O}(3^n n),空间复杂度为 O(n)\mathcal{O}(n)

【数据点 4∼94\sim 94∼9】

【题目描述】

数据点 464\sim 6 保证 k=0k=0;数据点 797\sim 9 保证 k1k\leq 1

【Hint】

先固定购买数量 xx。应当选择哪些糖果?若只能使用一个礼盒,又应当把哪两颗糖果放入礼盒?

【解法】

将价格排序为 a1a2ana_1\leq a_2\leq\cdots\leq a_n。当 k=0k=0 时,购买 xx 颗糖果的最小费用为

i=1xai.\sum_{i=1}^{x}a_i.

k=1k=1x2x\geq 2 时,把所选糖果中最贵的两颗装入礼盒,可以省去其中较便宜者的价格,最小费用为

i=1xaiax1.\sum_{i=1}^{x}a_i-a_{x-1}.

这一档已经说明:固定购买集合以后,应优先把较贵的糖果放入礼盒。

【数据点 10∼1510\sim 1510∼15】

【题目描述】

这一档允许使用多个礼盒,但 nn 的范围仍允许逐个计算每种购买数量的费用。

【Hint】

固定购买数量 xx,令

r=min(k,x2).r=\min\left(k,\left\lfloor\dfrac{x}{2}\right\rfloor\right).

价格均为正数,因此使用满 rr 个礼盒一定不劣。

【解法】

选择最贵的 2r2r 颗糖果装入礼盒,再把它们按照价格相邻配对。直接枚举 xx,并用 O(x)\mathcal{O}(x) 的时间计算费用,总时间复杂度为 O(n2)\mathcal{O}(n^2)

【正解】

【题目描述】

对每个 xx 求购买恰好 xx 颗糖果的最小费用,并判断它是否不超过 mm

【Hint】

证明三件事:

  1. 只需购买排序后的前 xx 颗糖果;
  2. 礼盒中装入最贵的 2r2r 颗糖果;
  3. 2r2r 颗糖果按照价格相邻配对。

【解法】

任意购买方案中的价格从小到大记为 c1,c2,,cxc_1,c_2,\ldots,c_x。全体糖果排序后有 aicia_i\leq c_i。单独结算与礼盒结算都只会取一个价格或两个价格的最大值,因此把每个 cic_i 替换成不大的 aia_i 不会增加费用。故存在一个最优方案,购买的恰好是前 xx 颗糖果。

若某颗较便宜的糖果在礼盒中,而一颗更贵的糖果单独结算,交换二者不会增加费用。因此可以令最贵的 2r2r 颗糖果全部进入礼盒。

对于 abcda\leq b\leq c\leq d,相邻配对的费用为 b+db+d,另外两种配对的费用均为 c+dc+d。不断消去最小的四颗待配对糖果,即可得到排序后相邻配对最优。

L=x2rL=x-2r。前 LL 颗糖果单独结算,后 2r2r 颗糖果相邻配对,因此

Cost(x)=i=1Lai+j=1raL+2j.\operatorname{Cost}(x) =\sum_{i=1}^{L}a_i+\sum_{j=1}^{r}a_{L+2j}.

第二项只取区间 (L,x](L,x] 中与 xx 奇偶性相同的位置。分别维护普通前缀和、奇数位置前缀和与偶数位置前缀和,即可在 O(1)\mathcal{O}(1) 时间内计算 Cost(x)\operatorname{Cost}(x)

【复杂度分析】

排序的时间复杂度为 O(nlogn)\mathcal{O}(n\log n),枚举购买数量的时间复杂度为 O(n)\mathcal{O}(n),空间复杂度为 O(n)\mathcal{O}(n)

【参考代码】

/*Author:EhundateghDate:2026/7/24Name:candy.cppYou steal,I kill.*/#include <cstdio>#include <cstring>#include <algorithm>#define MAXN 500010using namespace std;int T,n,k,Line[MAXN];long long m,Pre[MAXN],Odd[MAXN],Even[MAXN];long long Calc(int x){    int Count=min(k,x/2),Left=x-Count*2;    long long Ret=Pre[Left];    if(x&1) Ret+=Odd[x]-Odd[Left];    else Ret+=Even[x]-Even[Left];    return Ret;}void Solve(){    scanf("%d%lld%d",&n,&m,&k);    for(int i=1;i<=n;i++) scanf("%d",&Line[i]);    sort(Line+1,Line+n+1);    Pre[0]=Odd[0]=Even[0]=0;    for(int i=1;i<=n;i++){        Pre[i]=Pre[i-1]+Line[i];        Odd[i]=Odd[i-1];Even[i]=Even[i-1];        if(i&1) Odd[i]+=Line[i];        else Even[i]+=Line[i];    }    int Ans=0;    for(int i=1;i<=n;i++){        if(Calc(i)<=m) Ans=i;    }    printf("%d\n",Ans);    return;}int main(){    int c;    scanf("%d%d",&c,&T);    while(T-->0) Solve();    return 0;}