题目链接
题意简述
给出一个整数数集合 { a 1 , a 2 , ⋯ , a n } ( i ≠ j ⇒ a i ≠ a j ) \{a_1, a_2, \cdots, a_n\}\;(i\neq j \Rightarrow a_i\neq a_j) {a1,a2,⋯,an}(i=j⇒ai=aj),从中取出一个子集 S S S,如果对于这个子集,存在 m ≥ 2 m\geq 2 m≥2 使得子集中的所有的数模 m m m 意义下,同余,则称这个子集为友好子集,试求元素个数最多的友好子集有多少个元素。
输入格式
第一行一个正整数 T ( T ≤ 30 ) T\;(T \leq 30) T(T≤30),表示总共有 T T T 组数据。
每组数据的第一行包含一个整数 n ( n ≤ 2 × 1 0 5 ) n\;(n\leq 2\times 10^5) n(n≤2×105) 表示该集合的元素个数。
第二行输入 n n n 个整数表示 a 1 , ⋯ , a n ( a i ≤ 4 × 1 0 12 ) a_1, \cdots, a_n\;(a_i\leq4\times 10^{12}) a1,⋯,an(ai≤4×1012)。
数据保证 ∑ n ≤ 1 0 6 \sum n\leq 10^6 ∑n≤106。
解题思路
首先,当 m = 2 m=2 m=2 时,最大友好子集尺寸一定大于等于 n 2 \frac{n}{2} 2n(抽屉原理)。由此可见,整个问题的答案一定大于等于 n 2 \frac{n}{2} 2n。任取数列中的两个数做差,对这个差值进行质因数分解,不难说明,这个差值有不低于 25 % 25\% 25% 概率含有能够让友好子集取到最大的质因子 m m m。多随机取几对 { a i } \{a_i\} {ai} 中的数做差就能够有很大概率找到答案。
代码
#include <cstdio>
#include <set>
#include <cstdlib>
#include <ctime>
#include <map>
#include <cmath>
#include <algorithm>
using namespace std;
const int maxn = 200000 + 6;
long long A[maxn]; int ans = 0;
void test(long long m, long long base, const int n) {
int cnt = 0;
for(int i = 1; i <= n; i ++) {
if(abs(A[i] - base) % m == 0) cnt ++;
}
ans = max(ans, cnt);
if(m == 2) ans = max(ans, n - cnt); /// m = 2 时只能拆分出两个集合
}
int rand(int L, int R) { /// 生成 [L, R] 区间内的随机数
int RND = abs((rand() << 15) | rand());
return RND % (R - L + 1) + L;
}
void load(long long tmp, long long base, const int n) { /// 装载一个差值
if(tmp <= 1) return;
for(long long i = 2; i <= sqrt(tmp) + 0.5; i ++) {
if(tmp % i == 0) {
while(tmp%i == 0) tmp /= i;
test(i, base, n);
}
}
if(tmp != 1) test(tmp, base, n);
}
int main() {
srand(time(NULL)); /// 重置随机种子
int T; scanf("%d", &T);
while(T --) {
int n; scanf("%d", &n);
ans = 0;
for(int i = 1; i <= n; i ++) scanf("%lld", &A[i]);
test(2, 0, n); /// 2 一定是一个很优秀的解法
for(int t = 1; t <= 20; t ++) {
int l = rand(1, n-1), r = rand(l+1, n);
load(abs(A[l] - A[r]), A[l], n);
}
printf("%lld\n", ans);
}
return 0;
}
调参最后发现随机生成
20
20
20 组能过,
40
40
40 组常数太大,我的算法会 TLE
。