Codeforces Round 888 (Div. 3)F. Lisa and the Martians(异或,01字典树,排序)

文章讨论了一种编程问题,要求在一组数中找到两个不同的位置i和j,以及一个数x(x<2^k),使得(ai⊕x)&&(aj⊕x)最大。提出了两种解决方案,一种是使用01字典树进行查找和插入,另一种是直接排序数组并比较相邻元素的异或值。文章提供了C++代码实现这两种方法。
摘要由CSDN通过智能技术生成

题目链接:

888div3: (ai⊕x)&(aj⊕x)

题意:

在一组数中选取两个不同的位置i和j,找到一个x使得(ai⊕x)&(aj⊕x)最大,求出满足最小条件的x以及i,j;(以及x要满足x<2^k)

分析:

由于但ai=aj时可以使得答案取到最大,为2^k;(样例里都ai和aj都给的比较小)便很容易猜想到ai^aj最小时的情况去取x可以使得答案最大;

例如:在k的4情况下:ai和aj为一下两个数:

0101

0011

求使得答案最大的数,对于中间两位为0,1不同(从左往右2和3位置),所以对于x在那两个位置中不管取0还是1,去&后都为0,所以直接取0即可;

而对于两个数相同的0,1位置(1和4位置),对于0的位置取1,对于1的位置取0;

所以x=1000;满足最大值,最终(ai⊕x)&(aj⊕x)为:1001;

由上述推理也可以很好证明猜想是对的,尽量使得对应位置相同,那么就是异或值最小即可;

便可以将题目转化为求解数组中异或值最小的两个不同位置的下标

思路一:

采用01字典树进行求解:遍历数组,不断进行插入与查找,每次在插入当前位置的数之前进行查找与该数进行异或得到的最小的异或值,在利用异或性质:如果a^b=c,那么c^b=a;然后在用map数组不断更新保存数组中数的位置即可;

#include<bits/stdc++.h>
using namespace std;

#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
typedef pair<int,int> pr;

#define int long long
#define fr(i,l,r) for(int i=l;i<=r;i++)
#define pb(x) push_back(x)
#define all(a) a.begin(),a.end()
#define fi first
#define se second

const int N = 1e6+10;
const int mod=998244353,inf=LONG_LONG_MAX;
int dx[]={0,0,-1,0,1},dy[]={0,-1,0,1,0};
int n,m,k;

int a[N];

struct Trie {
    int n,idx;
    vector<vector<int>> Tr;
    Trie (int n) : n(n), Tr((n + 1) * 30,vector<int>(2,0)), idx(0) {}
    inline void insert(int x)
    {
        int p = 0;
        for(int i = 30;i >= 0;i --) {
            int nt = x >> i & 1;
            if(!Tr[p][nt]) Tr[p][nt] = ++ idx;
            p = Tr[p][nt];
        }
    }
    inline int query(int x) //和x异或后的最小值
    {
        int p=0,ans=0;
        for(int i=30;i >=0 ;i --){
            int bit=x >>i & 1;
            if(Tr[p][bit]) p=Tr[p][bit];//贪心先查找相同的数;
            else p=Tr[p][bit^1],ans+=1<<i;
        }
        return ans;
    }
};

void solve()
{
    cin>>n>>k;
    Trie v(n);

    int mi=inf,xi=1,yi=2;
    map<int,int>pos;
    fr(i,1,n)
    {
        int x;
        cin>>a[i];
        x=a[i];
        if(i>1){
            int y=v.query(x);
            if(y<mi)
            {
                mi=y,xi=i,yi=pos[x^y];//比较存储最小值位置
            }
        }
        v.insert(x);
        pos[x]=i;
    }
    int ans=0;
    fr(i,0,k-1)//求出最终的答案使得异或最大的x
    {
        if(!(a[xi]>>i & 1)&&!(a[yi]>>i & 1)) ans+=1<<i;
    }
    cout<<yi<<" "<<xi<<' '<<ans<<endl;
}

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

思路二:

首先有一个结论,最小异或对一定是集合排序后相邻的数.

我们只需要证明对于任意的a<b<c,都满足最小值一定是a^b或者b^c,而不可能是a^c.

满足这个条件后,如果我们选取的数在排序后的集合中不相邻,通过选相邻的数一定能使答案更小.

下面我们证明这个结论.

对于a^c,找到其二进制表示中最高的为1的位为第x位,则a和c高于x的位一定相同,第x位一定不同,因为a<c,一定是a第x位为0,c第x位为1.

因为a<b<c,高于x的位b一定也和a,c都相同,b的第x位可能为0或者为1.

所以a^b和b^c中一定有一个数第x位为00,而a^c第x位为1,即一定有一个数是小于a^c的,由此结论得证.

若无法理解证明也可以比较好的理解:相邻两个数的01大都都相同,异或会变得更小;与更远的一个数进行异或的话最高位是不一样的,导致异或值会更大;

对于这题将数直接进行排序比较相邻异或后的最小值,将最小值存储的位置记录一下就能够得到最终的答案,思路特别简单;

#include<bits/stdc++.h>
using namespace std;

#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
typedef pair<int,int> pr;

#define int long long
#define fr(i,l,r) for(int i=l;i<=r;i++)
#define pb(x) push_back(x)
#define all(a) a.begin(),a.end()
#define fi first
#define se second

const int N = 1e6+10;
const int mod=998244353,inf=LONG_LONG_MAX;
int dx[]= {0,0,-1,0,1},dy[]= {0,-1,0,1,0};
int n,k;
pr a[N];

void solve()
{
    cin >> n >> k;
    for(int i=1; i<=n; ++i)
    {
        cin >> a[i].first;
        a[i].second=i;
    }
    pr ans,pt;
    sort(a+1,a+1+n);
    int mn=2147483647;
    for(int i=1,v; i<n; ++i)
    {
        v=a[i].first^a[i+1].first;
        if(v<mn)
        {
            ans= {a[i].se,a[i+1].se};
            pt= {a[i].fi,a[i+1].fi};
            mn=v;
        }
    }
    int res=0;
    fr(i,0,k-1)
    {
        if(!(pt.fi>>i & 1)&&!(pt.se>>i & 1)) res+=1<<i;
    }
    cout << ans.fi << ' ' << ans.se << ' ' << res << endl;
}

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值