洛谷 P3650 [USACO1.3] 滑雪课程设计 Ski Course Design 题解
题意概述
给定一个长度为 \(n\) 的数列 \(\{a_i\}_{i=1}^n\),将 \(a_i\) 改为 \(b\) 所需的代价是 \((a_i-b)^2\)。求使得 \(\max\{a_i\}-\min\{a_i\}\le17\) 所需的最小代价。
解题思路
看到平方,我第一时间想到的就是三分。
如果不会三分法,右转 OI Wiki 学一学。
设要将数列的所有元素修改到 \([x,x+17]\) 区间内,问题转换为 \(x\) 在某个取值下总代价最小,求这个最小总代价。
于是有 \(x\in [\min\{a_i\},\max\{a_i\}-17]\),因为若 \([x,x+17]\) 的范围超出了数列的取值范围,则一定不会更优。
对于一个元素 \(a_i\) 所需的代价为:
\[cost_i=
\begin{cases}
(a_i - x)^2&(a_i<x),\\
0&(x\leq a_i\leq x + 17),\\
(a_i - x - 17)^2&(a_i>x + 17).
\end{cases}
\]
以 \(x\) 为自变量画出 \(cost_i\) 的图像,长这个样子:

很显然,这是一个单谷函数。
而总代价即为:
\[\sum_{i = 1}^{n}cost_i
\]
单谷函数的和还是单谷函数,可以直接使用三分法求最小代价。时间复杂度 \(O(n\log n)\),32ms 跑得飞起。
Code
#include <iostream>
using namespace std;
const int N=1000;
const int INF=0x3f3f3f3f;
int a[N],n;
int f(int x) // 计算区间取[x,x+17]时总代价
{
int s=0;
for(int i=0;i<n;++i)
{
if(a[i]<x) s+=(x-a[i])*(x-a[i]);
else if(a[i]>x+17) s+=(a[i]-x-17)*(a[i]-x-17);
}
return s;
}
int main()
{
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int l=INF,r=-INF,m1,m2;
cin>>n;
for(int i=0;i<n;++i)
{
cin>>a[i];
l=min(l,a[i]);
r=max(r,a[i]);
}
r-=17;
while(l<r) // 三分
{
m1=(l+r>>1),m2=m1+1;
if(f(m1)<f(m2)) r=m2-1;
else l=m1+1;
}
cout<<f(l);
return 0;
}
Update 2025.2.11:笔误,单峰函数 \(\to\) 单谷函数。
Update 2025.6.15:更换图片,更改图片与文字间距。
Update 2025.6.16:修改部分公式格式。

浙公网安备 33010602011771号