唯一一篇题解年代久远,我来补一篇
首先考虑我们怎么暴力的求解问题。显然可以枚举 m m m ,由于题目保证了 a a a 互不相同, m m m 的最小值一定在 [ n − k , 1 0 6 ] [n-k,10^6] [n−k,106] 中(为什么是 n − k n-k n−k 因为“余数互不相同”就代表至少要有可能出现的 n − k n-k n−k 种余数),每次暴力扫一遍,看有几个重复的,若小于 k k k 则就是答案。复杂度 O ( 1 0 6 n ) O(10^6 n) O(106n),由于 n ≤ 5000 n\le 5000 n≤5000,已 经 要 行 了。
再从别的角度考虑,我们要删除一个数,当且仅当它在模 m m m 意义下和别的数同余,那么两个数 a i a_i ai 和 a j a_j aj 同余的必要条件是什么?是 m m m 能整除 ∣ a i − a j ∣ |a_i-a_j| ∣ai−aj∣,那么进一步的,对于所有 i > j i>j i>j,如果我们能预处理出所有的 a i − a j a_i-a_j ai−aj(先进行排序),则删除一个数实质上是删除了一个被当前枚举的 m m m 整除的差。问题变为:删除至多 k k k 个数,使得剩下的数两两之差都不是 m m m 的倍数。
这时我就想到了一种错误的思路:我们预处理所有的 a i − a j a_i-a_j ai−aj,找到最小的 m m m 使得它最多整除 k k k 个差。很不幸这是错的,考虑这样一组样例:
4 2
2 3 5 7
这样如果你按照我说的思路答案应该是 3 3 3,但实际上答案是 2 2 2,为什么?因为其实所有的差值中有 3 3 3 个能被 2 2 2 整除的但其实两次确实可以消完。
那怎么办呢?我们重新审视最开始的暴力做法,利用刚刚的分析来剪枝。为什么之前的做法错误了?因为其实删除的数对数是不确定的,但是我们显然会发现这个 k k k 次删除的数对数有个上限,也就是最好情况下我们删除了 k k k 个数,这 k k k 个数两两之差都是 m m m 倍数,此时 k k k 次操作删除了 k ( k − 1 ) 2 \frac{k(k-1)}{2} 2k(k−1) 个数对,也就是如果此时枚举的 m m m 有超过 k ( k − 1 ) 2 \frac{k(k-1)}{2} 2k(k−1) 个数对是其倍数,肯定不是答案,可以直接剪枝掉。
由于唯一一篇题解没有代码,稍微放一下代码趴(我认为比较容易懂,所以就不解释了):
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=1e6;
int a[maxn+5],n,k,tot=0,b[maxn+5],s[maxn+5];
signed main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
sort(a+1,a+n+1);
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
s[(a[j]-a[i])]++;
}
}
for(int p=n-k;p;p++){
if(p>=maxn){
cout<<maxn<<endl;
return 0;
}
int sum=0;
for(int i=p;i<=maxn;i+=p){
sum+=s[i];
}
if(sum>k*(k+1)/2){
continue;
}
int cnt=0;
for(int i=1;i<=n;i++){
if(b[a[i]%p]==p){
cnt++;
if(cnt>k){
break;
}
}
b[a[i]%p]=p;
}
if(cnt<=k){
cout<<p<<endl;
return 0;
}
}
}