当前位置: 代码迷 >> 综合 >> 【Codeforces】1625D Binary Spiders 题解
  详细解决方案

【Codeforces】1625D Binary Spiders 题解

热度:28   发布时间:2024-01-21 21:06:40.0

题目大意

给定 n ( 2 ≤ n ≤ 3 × 1 0 5 ) n(2 \le n \le 3\times10^5) n(2n3×105)个数 a i ( 0 ≤ k ≤ 2 30 ? 1 ) ) a_i(0 \le k \le 2^{30}-1)) ai?(0k230?1))和自然数 k ( 0 ≤ a i ≤ 2 30 ? 1 ) k(0 \le a_i \le 2^{30}-1) k(0ai?230?1),挑出尽可能多的数,并且使得它们的疑惑和不小于 k k k,输出数量和挑选的数。
题目链接

思路

首先需要推出一个非常重要的结论:

n n n个数中挑出两个数,使得它们的异或和最小,那么这两个数一定是把这 n n n个数排序后相邻的数。

证明:假设有三个数 a < b < c a < b < c a<b<c:

  1. a ? c < b ? c a \oplus c < b \oplus c a?c<b?c,那么我们比较一下 b , c b,c b,c,二进制下从最高位往低位找,出现的第一个不同的位数,因为 a < b < c a < b < c a<b<c,所以这一位肯定是 c c c这一位为 1 1 1, a , b a,b a,b这一位为 0 0 0。所以即可推出 a ? b < a ? c a \oplus b < a \oplus c a?b<a?c

  2. a ? c > b ? c a \oplus c > b \oplus c a?c>b?c,我们只需要比较一下 a ? b , b ? c a \oplus b,b \oplus c a?b,b?c即可,最小值还是出现在相邻的数中的。

有了这个结论后,我们就可以继续做了,我们把 n n n个数从小到大排序后,从小到大遍历。假如我们已经找到了一个集合 s s s,我们遍历到了一个新的数 a i a_i ai?,想要判断它能否放入这个集合中,只需判断 a i ? m a x ( s ) ≥ k a_i \oplus max(s) \ge k ai??max(s)k即可,与集合中的其他数没有了关系。

因此,我们设 d p i dp_i dpi?表示以 a i a_i ai?为最大值的集合中最多有多少个数,
可以得到状态转移方程 d p j = m a x ( d p i ) + 1 , dp_j = max(dp_i) + 1, dpj?=max(dpi?)+1, 其中 a j ? a i ≥ k a_j \oplus a_i \ge k aj??ai?k

同时题目让我们输出每个被选中的数,所以我们在更新 d p j dp_j dpj?时,要记录下它的值是从哪里继承来的 p r e [ j ] = i pre[j] = i pre[j]=i

但是,直接进行状态转移的时间复杂度是 O ( n 2 ) O(n^2) O(n2),会超时,由于是异或操作,我们想到用 t r i e trie trie树来进行加速。

从最高位开始往树中插入。

我们遍历到了 a i a_i ai?,那么为了找到异或和大于 k k k的数,我们需要这么寻找:
设二进制下 a i a_i ai?这一位为 b i t bit bit,root为当前节点。

  • 如果这一位 k k k 1 1 1,那么我们只能往 t r i e [ r o o t ] [ b i t ? 1 ] trie[root][bit\oplus 1] trie[root][bit?1]

  • 如果这一位 k k k 0 0 0,那么我们有两种选择,选择 t r i e [ r o o t ] [ b i t ? 1 ] trie[root][bit\oplus 1] trie[root][bit?1],那么异或和就一定大于 k k k了,所以就没必要继续了,直接更新答案。或者选择 t r i e [ r o o t ] [ b i t ] trie[root][bit] trie[root][bit],继续往下寻找更新答案。

更新完 d p i dp_i dpi?后,我们把 a i a_i ai?插入树中,同时我们需要更新 f [ r o o t ] f[root] f[root],表示经过这个点的数中,集合最大的数是多少。

代码

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;const int maxN = 3e5 + 5;struct Num {
    int val, index;
}a[maxN];bool cmp(Num x, Num y)
{
    return x.val < y.val;
}int n, k, pre[maxN * 31], trie[maxN * 31][2], f[maxN * 31], d[maxN * 31], cnt = 1;int main()
{
    scanf("%d%d", &n, &k);for(int i = 1; i <= n; ++i) {
    scanf("%d", &a[i].val);a[i].index = i;}sort(a + 1, a + 1 + n, cmp);for(int i = 1; i <= n; ++i) {
    int ans = 0, root = 1;for(int j = 30; j >= 0 && root; --j) {
    int bit = (a[i].val >> j) & 1, rev = bit ^ 1;;if((k >> j) & 1)root = trie[root][rev];else {
    if(d[ans] < d[f[trie[root][rev]]])ans = f[trie[root][rev]];root = trie[root][bit];}if(j == 0 && d[ans] < d[f[root]])ans = f[root];}d[i] = d[ans] + 1; pre[i] = ans; root = 1;for(int j = 30; j >= 0; j--) {
    int bit  = (a[i].val >> j) & 1;if(!trie[root][bit])trie[root][bit] = ++cnt;root = trie[root][bit];if(d[f[root]] < d[i])f[root] = i;}}int ans = 1;for(int i = 2; i <= n; ++i)if(d[ans] < d[i])ans = i;if(d[ans] < 2)printf("-1\n");else {
    printf("%d\n", d[ans]);for(int i = ans; i != 0; i = pre[i])printf("%d ", a[i].index);}return 0;
}
  相关解决方案