P11671 [USACO25JAN] Farmer John's Favorite Operation S
题目描述
又是 Farmer John 的农场上寒冷而无聊的一天。为了打发时间,Farmer John 发明了一种关于在整数数组上进行操作的有趣的休闲活动。
Farmer John 有一个包含 \(N\)(\(1 \leq N \leq 2 \cdot 10^5\))个非负整数的数组 \(a\) 和一个整数 \(M\)(\(1 \leq M \leq 10^9\))。然后,FJ 会请 Bessie 给出一个整数 \(x\)。在一次操作中,FJ 可以选择一个索引 \(i\),并对 \(a_i\) 加 \(1\) 或减 \(1\)。FJ 的无聊值是他必须执行的最小操作次数,以使得对于所有的 \(1 \leq i \leq N\),\(a_i-x\) 均可被 \(M\) 整除。
对于所有可能的 \(x\),输出 FJ 的最小无聊值。
输入格式
输入的第一行包含 \(T\)(\(1 \leq T \leq 10\)),为需要求解的独立的测试用例数量。
每一个测试用例的第一行包含 \(N\) 和 \(M\)。
第二行包含 \(a_1, a_2, ..., a_N\) (\(0 \leq a_i \leq 10^9\))。
输入保证所有测试用例的 \(N\) 之和不超过 \(5 \cdot 10^5\)。
输出格式
对于每一个测试用例输出一行,包含对于所有可能的 \(x\),FJ 的最小无聊值。
输入输出样例 #1
输入 #1
2
5 9
15 12 18 3 8
3 69
1 988244353 998244853
输出 #1
10
21
说明/提示
样例解释
在第一个测试用例中,\(x\) 的一个最优选择是 \(3\)。FJ 可以执行 \(10\) 次操作使得 \(a = [12, 12, 21, 3, 12]\)。
子任务
- 测试点 2:\(N \le 1000\) 以及 \(M \le 1000\)。
- 测试点 3:\(N\le 1000\)。
- 测试点 4-5:\(M\le 10^5\)。
- 测试点 6-16:没有额外限制。
解题报告
这题应该可以场切的,可恶啊!!!
首先排除一下干扰项 \(x\),实际上题目就是求一个最少的操作次数,使每个数在模 \(M\) 的意义下同余。
由于我们只关心余数,一开始我们就将每个 \(a_i\) 模去 \(M\),用余数数组 \(x\) 处理,目标就是使数组 \(x\) 相同。
用到一个结论:最优的情况肯定是将所有数调成选取数组 x 的中位数。
怎么证明?假设一个长度为奇数的不降数列 \(x\),其中位数为 \(t\),那么显然大于 \(t\) 的数和小于 \(t\) 的数的个数相等,设其个数为 \(cnt\),现在尝试将左移一位到数 \(s\),那么位于 \(s\) 右边的数的总花费增加 \((cnt+1) \times (t-x)\),位于 \(s\) 左边的数的总花费减少 \(cnt \times (t-x)\),显然花费更多了,所以选中位数是最优解。对于偶数长度的数列,可证选中间的两个数均为最优解。
但是对于这题还有其他可能:在模 \(M\) 的意义下,一个数调为中位数 \(t\)、\(t+m\) 或 \(t-m\) 都可以完成同余,并且可能会更优。这可以看成所有的数都在一个模 \(M\) 意义下的余数环上,那么实际上每个数都可以成为中位数。
在余数环上,我们所求的实际上变成了:枚举每一个数,将其他所有数在余数环上调成这个数,求出其中的最优解。
处理的方法是:采取类似破环成链的方法,排序后将每个数加上 \(M\) 并依次加到原数组后面,查出每个长度为 \(n\) 的区间调成中位数的花费最小值。
为什么这样写?假设我们选择将所有数调成 \(x_p\),如果存在一个 \(x_i < x_p\) 调成 \(x_p-M\) 更优,那么和将 \(x_i+M\) 调成 \(x_p\) 等价且最优花费相同;如果存在一个 \(x_i > x_p\) 调成 \(x_p+M\) 更优,我们直接将 \(x_p\) 变成 \(x_p+M\) 来处理。
代码如下:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int INF=LONG_MAX;
const int N=501100;#define ckmax(x,y) ( x=max(x,y) )
#define ckmin(x,y) ( x=min(x,y) )inline int read()
{int f=1,x=0; char ch=getchar();while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }while(isdigit(ch)) { x=x*10+ch-'0'; ch=getchar(); }return f*x;
}int n,m;
int x[N],s[N];signed main()
{int Q=read();while(Q--){n=read(),m=read();int ans=INF;for(int i=1;i<=n;i++) x[i]=read()%m;sort(x+1,x+n+1);for(int i=1;i<=n;i++) x[i+n]=x[i]+m;for(int i=1;i<=2*n;i++) s[i]=s[i-1]+x[i];for(int i=1;i<=n;i++){int l=i,r=i+n-1,mid=l+r>>1;int pre=(mid-l)*x[mid]-(s[mid-1]-s[l-1]);int suf=(s[r]-s[mid])-(r-mid)*x[mid];ckmin(ans,pre+suf);}printf("%lld\n",ans);}return 0;
}