最大子集(枚举,手写哈希,推公式)

题意

给定一个包含\(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;
}
posted @ 2022-08-07 11:10  pbc的成长之路  阅读(79)  评论(0编辑  收藏  举报