题解:[JOI 2026 二次预选] 购物 3 / Shopping 3
题意分析
询问之间独立,分开考虑 \(c,d\)。
答案与 \(a_i\) 顺序无关,不妨令 \(a_1,a_2,\cdots,a_n\) 单调不降。
单次询问答案为:
\[\min_{k=0}^{\lceil\frac{a_n}{d}\rceil}\left(\sum_{i=1}^n\max(a_i-kd,0)+kc\right)
\]
暴力做复杂度肯定是错的。
考虑抛开 \(\min\) 的限制,答案是一个函数:
\[\sum_{i=1}^n\max(a_i-kd,0)+kc
\]
因为 \(\displaystyle\sum_{i=1}^n\max(a_i-kd,0)\) 随 \(k\) 增加而单调不升,\(kc\) 随 \(k\) 增加而单调不降。两个单调函数加到一起,可以得到一个单峰函数。
显然是一个凸函数,随 \(k\) 增加先单调不升再单调不降。
然后模拟赛时想到某 OI 书籍的一句话:
二分利用的是函数的单调性,三分利用的是函数的单峰性。
然后写了一个三分,挂了,被 JOI 绑包得到了 \(6\) 分的高分。
事实上三分的正确性不太能保证,因为可能会存在平台,然后就炸完了。
处理方法是三分到区间长度足够小的时候(例如 \(\leq10\)),暴力扫一遍求最小值。
AC 代码
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
#include<random>
using namespace std;
typedef long long ll;
#define int ll
constexpr const int N=300000+1000,Q=300000+1000;
constexpr const ll inf=0x3f3f3f3f3f3f3f3f;
constexpr const double eps=1e-7;
mt19937 Rand(time(0));
int n,q,a[N+1],c[Q+1],d[Q+1];
ll pre[N+1];
ll calc(int j,int k){
ll pl=pre[n]+k*(c[j]-(n+1ll)*d[j]);
int p=upper_bound(a+1,a+n+1,1ll*k*d[j])-a;
return pl+1ll*p*k*d[j]-pre[p-1];
}
main(){
/*freopen("test.in","r",stdin);
freopen("test.out","w",stdout);*/
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>q;
for(int i=1;i<=n;i++){
cin>>a[i];
}
sort(a+1,a+n+1);
for(int i=1;i<=n;i++){
pre[i]=pre[i-1]+a[i];
}
for(int i=1;i<=q;i++){
cin>>c[i]>>d[i];
}
for(int j=1;j<=q;j++){
int l=0,r=(a[n]+d[j]-1)/d[j];
int d=max(1ll,(r-l)>>2),k=l+r>>1;
ll ans=min(pre[n],calc(j,k));
while(d){
int k1=max(l,k-d),k2=min(k+d,r);
ll pl1=calc(j,k1),pl2=calc(j,k2),pl=calc(j,k);
if(pl1<=pl2&&pl1<pl){
k=k1;
ans=min(ans,pl1);
}else if(pl2<pl&&pl2<=pl1){
k=k2;
ans=min(ans,pl2);
}else{
d/=2;
}
}
cout<<ans<<'\n';
}
cout.flush();
/*fclose(stdin);
fclose(stdout);*/
return 0;
}
/*
10 1
9628 862 6594 1995 5388 4401 3594 3580 3075 1880
11444 3009
26641
*/

浙公网安备 33010602011771号