Farmer John's Favorite Operation (USACO Silver 2025 January)
这个题解法简单,但是理解起来不太直观(出题人应该是先想好的解法,再编写题目的),官方题解也写得很绕,所以是一道考验思维能力的题目。下面是一种解释的方法,供参考。
,然后从小到大排序,然后我们断言在以下数字中:
这个成立的原因如下:
- 原题要求操作之后的可以被整除,这个操作的次数就是,其中。的意思,就是当小于的时候,我们把移动变成0,而当大于的时候,我们把它移动变成,这样可以让操作次数最小。
- 按这个定义,我们把变成,对于上面1里面的计算是没有影响的:
第二步第二第三项,分别当和时成立。这三项,分别对应上面“断言”中的三行。
- 因为随着的变化,整体ans也是线性变化的,所以极值只会取在端点,也就是x只需要取这些值。
- 对于一个具体的,我们观察上面表达式中的三项,分别是与的距离,与的距离,与的距离。那么最后的ans就是以为中心,上面的数组中上下各的范围内的数字与的距离之和。
所以我们只需要在以上范围内枚举,然后计算以为中心,上下的范围内的数字与的距离之和(下限,上限), 然后取最小值就可以了。使用prefix sum,可以在时间内完成。再加上排序时间,最后复杂度是。
(以下程序上实现中,实际只使用了这个值,这个可以证明和这里讲的个值是一样的,具体证明略过,复杂度一样)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int t, n;
ll m;
ll a[400005], b[400005]; // b is prefix sum of a
int main() {
scanf("%d",&t);
while(t--) {
scanf("%d%lld",&n, &m);
for(int i=0;i<n;++i) {
scanf("%lld", &a[i]);
a[i] %= m;
}
sort(a, a+n);
for (int i = 0; i < n; i++)
a[i+n] = a[i] + m;
for(int i=0;i<2*n;++i)
b[i+1] = b[i] + a[i];
int li = 0, ri = 0;
ll ans = 1e18;
for (int i = 0; i < 2*n; i++) {
ll l = a[i]-m/2;
ll r = l+m;
if (l < 0) continue;
if (r >= 2*m) continue;
while (a[li] < l) li++;
while (a[ri] < r && ri < 2*n) ri++;
ll v = (i-li)*a[i] - (b[i]-b[li]); // sum of abs(a[j]-a[i]) from li to i-1
v += (b[ri]-b[i+1]) - (ri-i-1)*a[i]; // sum of abs(a[j]-a[i]) from i+1 to ri
ans = min(ans, v);
}
printf("%lld\n",ans);
}
return 0;
}