最大子集(枚举,手写哈希,推公式)
题意
给定一个包含\(n\)个元素的整数集合。
集合中的元素两两不同。
请你找到一个该集合的最大子集,要求子集内的元素满足任意两元素之差的绝对值都是\(2\)的整数幂。
注意,只包含\(1\)个元素的子集一定满足条件。
题目链接:https://www.acwing.com/problem/content/4508/
数据范围
\(1 \leq n \leq 2 \times 10^5\)
\(-10^9 \leq x_i \leq 10^9\)
思路
首先我们分析一下子集中任意两元素之差的绝对值都是\(2\)的整数幂具有什么性质。
假设子集中的任意\(3\)个元素,从小到大为\(x, x + 2^{d_1}, x + 2^{d_1} + 2 ^ {d_2}\)。
\(2^{d_1} + 2 ^ {d_2} = 2^{d_1} (1 + 2^{d_2 - d_1})\)为\(2\)的整数次幂,因此必然有\(d_1 = d_2\)。
如果集合中超过\(3\)个数,那么必然不可能满足要求。所以,子集元素个数不会超过\(3\)。
我们可以枚举\(3\)个元素中的最大值,然后再枚举公差即可。
这里由于数据范围较大,不能直接使用STL的map,因此需要手写哈希表。
代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 200010, M = 1999997, INF = 0x3f3f3f3f;
int n;
int q[N], h[M];
int find(int x)
{
int t = (x % M + M) % M;
while(h[t] != INF && h[t] != x) {
if(++ t == M) {
t = 0;
}
}
return t;
}
int main()
{
scanf("%d", &n);
for(int i = 0; i < n; i ++) scanf("%d", &q[i]);
sort(q, q + n);
memset(h, 0x3f, sizeof h);
int res[3], s[3];
int rt = 0, st = 0;
for(int i = 0; i < n; i ++) {
for(int j = 0; j <= 30; j ++) {
int d = 1 << j;
s[0] = q[i], st = 1;
for(int k = 1; k <= 2; k ++) {
int x = q[i] - d * k;
if(h[find(x)] == INF) break;
s[st ++] = x;
}
if(rt < st) {
rt = st;
memcpy(res, s, sizeof s);
if(rt == 3) break;
}
}
if(rt == 3) break;
h[find(q[i])] = q[i];
}
printf("%d\n", rt);
for(int i = 0; i < rt; i ++) printf("%d ", res[i]);
return 0;
}