题解:P10989 [蓝桥杯 2023 国 Python A] 等腰三角形

· · 题解

upd:8.29一个细节挂了被撤回了。
根据等腰三角形的性质,满足的序列一定只有一种数字,两种不同的数字。一种数字比较 trivial,下面只考虑两种数字的。设这两种数字分别为 x,y,不防钦定大小关系 x<y, 则还需要满足 x+x>y

假设我们一开始就钦定最终的 x,y ,那么需要的代价是

\sum_i \min(|a_i-x|, |a_i-y|)

我们把数字全放在数轴上,这个式子的含义就是:在这个数轴上选取两个原点 (也就是上文中的x,y),求和数轴上的点到这两个原点距离的较小值。
一个显然的想法是,一定能分成两部分,使得左半部分到达 x 点更近,而右半部分到达 y 更近。
我们枚举这个分界线,对于左边部分,显然是取到中位数的点最优,右半部分同理。
为什么要取中位数?学过初中的零点分段都知道....

如果满足 x+x>y 则直接更新答案,但这样得出的 x,y 并不一定满足 x+x>y 的要求。

如果不满足,则我们需要 x 向右调整,或者 y 向左调整。 如果 x 向右调整了 k 格,那么 y 能取到的范围可以确定,即

y<2\times(x+k)

而由于越靠近中位数,所求式子越小,我们可以直接令

y'=\min(y, 2\times(x+k)-1)

因此我们把调整后的代价看做一个函数 f(k) ,通过人类智慧可以猜出他是单谷函数,因此可以用三分解决。
既然这是题解那我们还是需要证明一下的。
考虑设

f(k)=L(k)+R(k) 直到这里,我们可以设计程序了。 1.枚举一个分割点,分成左部分和右部分。 2.求出两部分的中位数,看看是否合法。 3.若合法,直接更新答案,否则三分求出最小的点,再更新。 但这题还有一个 corner case,若最终变成的 $x,y$ 其中**成为** $x$ 的只有一个数,则不需要 $x+x<y$ 的限制,特判掉即可。 有点省选联考 D1T1 的感觉。 ```cpp #include<bits/stdc++.h> using namespace std; const int N=2e5+5, inf=1e9; using ll=long long; int n, a[N]; ll ans=1ll*inf*inf, s[N]; ll calc(int l, int r, int x) { int f=upper_bound(a+l, a+r+1, x)-a-1; ll lp=f-l+1, rp=r-f; return (lp*x-(s[f]-s[l-1]))+(s[r]-s[f]-rp*x); } void solve(int mid, int x, int y) { int l=0, r=inf; while(l+20<=r) { int lp=l+(r-l)/3; int rp=lp+(r-l)/3; ll lans=(calc(1, mid, x+lp)+calc(mid+1, n, min(x+lp+x+lp-1, y))), rans=(calc(1, mid, x+rp)+calc(mid+1, n, min(x+rp+x+rp-1, y))); if(lans<rans) r=rp; else l=lp; } ll res=1ll*inf*inf; for(int i=l; i<=r; i++) { res=min(res, calc(1, mid, x+i)+calc(mid+1, n, min(x+i+x+i-1, y))); } ans=min(ans, res); return ; } int main() { scanf("%d", &n); for(int i=1; i<=n; i++) scanf("%d", &a[i]); sort(a+1, a+1+n); for(int i=1; i<=n; i++) s[i]=s[i-1]+a[i]; for(int i=1; i<n; i++) { int x=a[i/2+1], y=a[(n-i+1)/2+i]; if(x+x>y) ans=min(ans, calc(1, i, x)+calc(i+1, n, y)); else solve(i, x, y); } ans=min(ans, calc(2, n, a[1+(n-1)/2])); printf("%lld\n", ans); return 0; } ```