Codeforces Round 888 (Div. 3)F题题解

Lisa and the Martians

在这里插入图片描述在这里插入图片描述

问题建模

​ 给定n个数和k,每个数小于 2 k 2^k 2k,问哪两个数分别与一个小于 2 k 2^k 2k的数x进行异或运算后再进行与运算的值最大,输出这两个数的编号和x。

问题分析

1.分析给定的运算操作

我们由外向内进行分析,运算最外层为两个数进行与运算,只有两个数对应位都为1才能使最终结果的对应位为1。接着考虑更里面一层中,异或上怎么样的x才能使两个数对应位都为1。若两个数相同,则异或的x对应位为0或者1即可让得到的两个数对应位都为1,若不同则无论x的对应位为什么值,都无法使得最终得到的两个数对应位同时为1。则我们需要找两个数其高位到低位的对应位尽可能相同,才能使得最终的运算结果尽可能大。

2.方法1使用Trie树来查找最符合的数

使用01-Trie对于输入的每个数,在已输入数中查找与该数高位尽可能相同的数,然后计算对应的结果,将该数放入Trie中,最终输出最大值对应使用数值的编号和x。

代码
#include<bits/stdc++.h>

#define x first
#define y second
using namespace std;
typedef unsigned long long ULL;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
const int N = 2e5 + 10, M = N << 6, Mod = 998244353;
int a[N];
int son[M][2],idx;
int cnt[M];
int n,k;

///将x的编号插入到01-Trie中
void insert(int x, int pos) {
    int p = 0;
    ///由高位向低位检索
    for (int i = k - 1; ~i; i--) {
        int &s = son[p][(x >> i) & 1];
        if (!s)  s = ++idx;
        p = s;
    }
    cnt[p]=pos;
}

///检索与x高位尽可能相同的值的编号
int search(int x) {
    int p = 0;
    for (int i = k - 1; ~i; i--) {
        int s =(x>>i)&1;
        if (son[p][s]) {
            p = son[p][s];
        } else p = son[p][!s];
    }
    return cnt[p];
}

void solve() {
    cin >> n >> k;
    for (int i = 0; i < n; i++) {
        scanf("%d", &a[i]);
    }
   
    ///初始化01-Trie的起始编号和各个结点
    idx = 0;
    ///0是起始结点,此外每个数有k位,总共有n个数,每个数的每一位为1个结点,则总共有n*k+1个结点
    for (int i = 0; i <= n * k; i++) {
        son[i][0] = son[i][1] =0;
    }

    int ai = -1, aj = -1, ax = -1, ans = -1;
    for (int i = 0; i < n; i++) {
        if (i) {///第一个数插入后,再进行查询操作,否则会查询到未被初始化的索引
            int j = search(a[i]);
            ///计算当前两个元素a[i]和a[j]进行运算后的最终结果
            ///两个元素对应位置若相同则通过异或变成0,然后取反,再将其限制在k位内,就是最终结果
            int nans = (~(a[i] ^ a[j])) & ((1 << k) - 1);
            if (nans > ans) {
                ans = nans;   
                ///ax为当前两个元素进行运算所需的x
                ///结果位最终若为1,两个数对应位一定相同,则其异或值x对应位一定与两个数对应位相反,则通过异或运算结果位于其中一个数可得到x该位对应的值,再将其限制在k位内,就是最终的x
                ai = i, aj = j, ax =(nans^a[i])&((1<<k)-1);
            }
        }
        insert(a[i], i);
    }
    cout <<aj+1  << " " << ai+1  << " " << ax << "\n";
}

int main() {
    int t = 1;
    cin >> t;
    while (t--) solve();
    return 0;
}

方法2通过性质,排序后找运算的最大值

因为我们要找高位到低位尽可能相同的两个数,这样运算结果最大,则这样的两个数若做异或运算其结果为最小值。则问题变成了找到两个做异或运算最小的数。而做异或运算最小的两个数,必定是相邻的,因为相邻的数其高位到低位相同的比较多则异或后高位到低位为0的也多,则可以排序所有元素后,计算相邻两个数的异或值找最小的即可。(该性质证明放在最后面。)

代码
#include<bits/stdc++.h>

#define x first
#define y second
using namespace std;
typedef unsigned long long ULL;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
const int N = 2e5 + 10, M = N << 6, Mod = 998244353;
PII p[N];
bool cmp(PII &p1, PII &p2) {
    return p1.x < p2.x;
}

void solve() {
    int n,k;
    cin >> n >> k;
    for (int i = 1; i <= n; i++) {
        scanf("%d", &p[i].x);
        p[i].y=i;
    }
    sort(p+1,p+1+n,cmp);

    int pos=-1,ans=((1<<k)-1);///初始时k位上都为1
    for(int i=2;i<=n;i++){
        int nans=(p[i].x^p[i-1].x);
        if(nans<=ans)    pos=i,ans=nans;
    }

    int ai=p[pos-1].x,aj=p[pos].x;
    int res= (~(ai ^ aj)) & ((1 << k) - 1);///使用方法1中使用的计算方式计算x
    cout <<p[pos-1].y<<" " <<p[pos].y<<" "<<((res^ai)&((1<<k)-1)) <<"\n";
}

int main() {
    int t = 1;
    cin >> t;
    while (t--) solve();
    return 0;
}

证明

证明:min( x ⨁ y x\bigoplus y xy y ⨁ z y\bigoplus z yz )< x ⨁ z x\bigoplus z xz,当x,y,z都为正整数且(x<y<z)

假设最高位到第i+1位,x,y,z都相同,z的第i位为1,x的第i位为0

  1. 若y第i位为1,则有 y ⨁ z y\bigoplus z yz< x ⨁ z x\bigoplus z xz
  2. 若y第i位为0,则有 x ⨁ y x\bigoplus y xy< x ⨁ z x\bigoplus z xz

所证完毕,则通过该证明,可以说明一个数异或值最小的数为异或上其相邻的数。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值