提供一种单组数据 \(\mathcal O(n)\) 的做法。
先把绝对值去掉,这样每个下注建议就是在 \(\{x-a_i,a_i-x,0\}\)(假设最终排名为 \(x\))选一个。
若选择 \(\{x-a_i,a_i-x,0\}\) 的建议集合分别为 \(A,B,C\),那么最终收益为:
\[f(x)=\sum_{i\in A} (x-a_i)+\sum_{i\in B} (a_i-x)
\]
将 \(x\) 拆出来:
\[f(x)=(|A|-|B|)x+\sum_{i\in B} a_i-\sum_{i\in A} a_i
\]
要求的答案即 \(\min_{i=l}^r f(i)\)。
可以发现 \(f(i)\) 是一个一次函数,最值在区间的端点处取到,因此答案为 \(\min(f(l),f(r))\)。
考虑让斜率 \(|A|-|B|\) 的绝对值尽可能小,并且 \(\sum_{i\in B} a_i-\sum_{i\in A} a_i\) 尽可能大,这样显然是最优的。
于是可以将 \(a\) 数组排序,前一半取 \(x-a_i\),后一半取 \(a_i-x\)。此时 \(x\) 为中位数时有最小值。
(以上是感性理解,如果有漏洞或更严谨证明欢迎指教。)
#include<bits/stdc++.h>
#define N 200005
using namespace std;
int n,l,r,a[N];
int main(){int T;scanf("%d",&T);while(T--){long long ans=0;scanf("%d%d%d",&n,&l,&r);for(int i=1;i<=n;i++) scanf("%d",a+i);nth_element(a+1,a+(n+1>>1),a+n+1);int x=a[n+1>>1];x=max(x,l),x=min(x,r);for(int i=1;i<=n;i++) ans+=abs(a[i]-x);printf("%lld\n",ans);}return 0;
}