跳到主要内容
原文398 词数2 分钟

题解:P14635 [NOIP2025] 糖果店

概述

n 种糖果库存无限,第 i 种的售价按 x_i、y_i 交替循环,求 m 元最多买多少颗。把每种糖果拆成售价 x_i、限购一组的单颗装,和售价 x_i 加 y_i、不限组数的两颗装,两类独立分别排序。花费随数量单调递增,对购买总数二分答案;算买 k 颗的最小花费时枚举两颗装组数 p,单颗装取前缀和,两颗装取最便宜的一种乘以 p。时间复杂度 O(nlogm),需要使用 128 位整数防止溢出。

题意简述​

给定 nn 种糖果,每种库存无限。第 ii 种糖果的售价循环为 xi,yi,xi,yi,…x_i,y_i,x_i,y_i,\dots。求 mm 元最多能购买多少颗糖果。

解题思路​

考场思路。

我们可以将每种糖果拆分为两类 套装:

  • 单颗装:售价 ai=xia_i=x_i 元,限购 11 组;
  • 两颗装:售价 bi=xi+yib_i=x_i+y_i 元,不限组数。

由于两类套装的相互独立,可以将 aa 和 bb 分别排序。

显然花费随着糖果数量增加 单调递增,我们可以对购买数量 kk 进行 二分答案。

计算购买 kk 颗糖果的最小花费:将 kk 拆分为 k=2p+qk=2p+q,即购买 pp 组两颗装和 qq 组一颗装。

考虑两类套装的最小花费:

  • 单颗装:选择 aia_i 最小的前 qq 种,即 aia_i 的前缀和 sqs_q;
  • 两颗装:选择 pp 组 bib_i 最小的,即 pb1pb_1。

枚举两颗装组数 pp,得到单颗装组数 q=k−2pq=k-2p,此时的总花费为:

pb1+sqpb_1+s_q

关于 pp 的范围:由于 k=2p+qk=2p+q,而 0≤q≤n0\le q\le n,因此 0≤k−2p≤n0\le k-2p\le n,解得:

max⁡(⌈k−n2⌉,0)≤p≤⌊k2⌋\max\left(\left\lceil\frac{k-n}{2}\right\rceil,0\right)\le p\le\left\lfloor\frac{k}{2}\right\rfloor

时间复杂度为 O(nlog⁡m)O(n\log m),注意这个方法要用 __int128。

半小时过 T1,罚坐四小时。

参考代码​

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

using ll=long long;
const ll inf=0x3f3f3f3f3f3f3f3f;
const int N=100005;
ll a[N],b[N];
ll n,m;
bool check(ll k)
{
__int128 mn=inf;
for(ll p=max(k-n+1>>1,0ll);p<=k>>1;p++)
{
mn=min(mn,__int128(p)*b[1]+a[k-p*2]);
}
return mn>m;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>a[i]>>b[i];
b[i]+=a[i];
}
sort(a+1,a+n+1);
sort(b+1,b+n+1);
for(int i=1;i<=n;i++)a[i]+=a[i-1];
ll l=0,r=inf;
while(l<r)
{
ll mid=l+r>>1;
if(check(mid))r=mid;
else l=mid+1;
}
cout<<l-1<<'\n';
return 0;
}