Xor Sum HDU - 4825

本文介绍了一道经典的字典树模板题,通过数组和指针两种方式实现字典树来解决该问题。文章详细解释了如何利用字典树进行更新和查询操作,并提供了完整的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

http://acm.hdu.edu.cn/showproblem.php?pid=4825

01字典树模板题

贪心的考虑 肯定要先使最高位为1 在这基础上递归考虑低位 所以插入和查询时都是高位开头

 

数组版

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

struct node
{
    int c[2];
    int cnt;
};

node tree[3000010];
ll pre[50];
int n,q,num;

void init()
{
    int i;
    pre[0]=1;
    for(i=1;i<=31;i++) pre[i]=2ll*pre[i-1];
}

void update(int val)
{
    int cur,i,id;
    cur=0;
    for(i=31;i>=0;i--)
    {
        id=1&(val>>i);
        if(tree[cur].c[id]==0) tree[cur].c[id]=num++;
        tree[tree[cur].c[id]].cnt++;
        cur=tree[cur].c[id];
    }
}

ll query(ll val)
{
    ll res;
    int cur,i,id;
    cur=0,res=0;
    for(i=31;i>=0;i--)
    {
        id=1&(val>>i);
        if(tree[cur].c[id^1])
        {
            cur=tree[cur].c[id^1];
            res+=(id^1)*pre[i];
        }
        else
        {
            cur=tree[cur].c[id];
            res+=id*pre[i];
        }
    }
    return res;
}

int main()
{
    ll val;
    int t,cas,i;
    init();
    scanf("%d",&t);
    for(cas=1;cas<=t;cas++)
    {
        scanf("%d%d",&n,&q);
        memset(tree,0,sizeof(tree));
        num=1;
        for(i=1;i<=n;i++)
        {
            scanf("%lld",&val);
            update(val);
        }
        printf("Case #%d:\n",cas);
        while(q--)
        {
            scanf("%lld",&val);
            printf("%lld\n",query(val));
        }
    }
    return 0;
}

 

指针版

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

struct node
{
    node *next[2];
    bool cnt;
};

node *root;
int n,q;

void getnode(node *&ptr)
{
    int i;
    ptr=new node;
    for(i=0;i<2;i++) ptr->next[i]=NULL;
    ptr->cnt=0;
}

void update(node *ptr,int *ary,int cur,int len)
{
    if(ptr->next[ary[cur]]==NULL) getnode(ptr->next[ary[cur]]);
    if(cur==len-1)
    {
        ptr->next[ary[cur]]->cnt=1;
        return;
    }
    update(ptr->next[ary[cur]],ary,cur+1,len);
}

ll query(node *ptr,int *ary,ll val,int cur,int len)
{
    if(cur==len) return 0;
    if(ptr->next[ary[cur]^1]!=NULL) return val*(ary[cur]^1)+query(ptr->next[ary[cur]^1],ary,val/2,cur+1,len);
    else return val*ary[cur]+query(ptr->next[ary[cur]],ary,val/2,cur+1,len);
}

int main()
{
    ll val;
    int ary[50];
    int t,cas,i;
    scanf("%d",&t);
    for(cas=1;cas<=t;cas++)
    {
        getnode(root);
        scanf("%d%d",&n,&q);
        while(n--)
        {
            scanf("%lld",&val);
            for(i=34;i>=0;i--,val/=2) ary[i]=val%2;
            update(root,ary,0,35);
        }
        printf("Case #%d:\n",cas);
        while(q--)
        {
            scanf("%lld",&val);
            for(i=34;i>=0;i--,val/=2) ary[i]=val%2;
            printf("%lld\n",query(root,ary,17179869184ll,0,35));
        }
    }
    return 0;
}

 

<think>嗯,用户让我解析这段Java代码,特别是int lsb = ...这一行。首先,我得回忆一下这个算法的整体思路。题目是找出数组中只出现一次的两个数字,其他数字都出现两次。这种情况下,通常会用异或操作,因为相同的数字异或会抵消,结果为0。但如果有两个不同的单独数字,异或的结果会是这两个数的异或值,所以需要进一步处理。 那代码的大致流程应该是这样的:首先,对所有数进行异或,得到xorsum,这个xorsum实际上就是那两个单独数字的异或结果。因为其他数字成对出现,异或后为0。接下来的问题是如何把这两个数字分开。这时候需要找到xorsum中的一个二进制位,这个位在两个单独数字中一个是1,另一个是0。这样就能将原数组分成两组,每组各包含一个单独数字,然后分别异或处理。 现在关键点在于如何找到这样的一个二进制位。代码中用了xorsum & (-xorsum)来获取最低有效位(LSB),也就是最右边的1所在的位。但这里有个特殊情况判断,如果xorsum是Integer.MIN_VALUE,即二进制1000...000,这时候xorsum本身就是负数,取负数的话可能会溢出?或者是因为在这种情况下,xorsum & (-xorsum)的结果是否正确? 比如,当xorsum是Integer.MIN_VALUE时,-xorsum在Java中会溢出,因为int的范围是-2^31到2^31-1,所以Integer.MIN_VALUE的绝对值超过了最大正数,所以-xorsum的结果还是Integer.MIN_VALUE?这时候xorsum & (-xorsum)就等于xorsum本身,所以这种情况下直接取xorsum。否则的话,用xorsum & (-xorsum)来得到LSB。 这一步的意义是为了找到两个单独数字不同的最低有效位,这样可以根据这一位将数组分成两部分。然后对这两部分分别进行异或,最终得到两个单独的数字。 那用户的问题主要是这一步为什么要这么做,以及为什么要处理Integer.MIN_VALUE的情况。可能用户对位运算中的负数处理不太清楚,或者对溢出情况有疑问。需要解释清楚补码表示下的负数,以及如何用x & -x来得到最低有效位的原理。 可能还需要举例子说明。比如,假设xorsum是某个数,比如6(二进制0110),那么-xorsum在补码中是1010(假设4位的话),然后x & -x得到0010,即最低位的1。这时候就能用这个位来区分两个不同的数字。 但当xorsum是Integer.MIN_VALUE时,即二进制1后面31个0,那么-xorsum在补码中是无法表示的,因为int的范围到不了+2^31,所以这时候的-xorsum会溢出,导致结果还是Integer.MIN_VALUE本身?或者可能这时候的计算结果是否正确? 比如,在Java中,对于Integer.MIN_VALUE取负数,实际上还是Integer.MIN_VALUE,因为溢出。所以在xorsum为Integer.MIN_VALUE时,xorsum & (-xorsum)的结果就是xorsum本身,这时候作为掩码是否正确?这个时候只有最高位是1,所以根据这个掩码分组的话,其他数的该位是否为1会被分组。这时候是否正确? 比如,假设两个单独的数字是x和y,它们的异或结果是x ^ y = Integer.MIN_VALUE,即最高位不同。这时候用这个掩码的话,x和y在这一位一个是1,另一个是0。所以这时候的分组是正确的,可以正确地将两个数分开。因此,这一步的处理是正确的,不需要特殊处理?或者可能当xorsum是Integer.MIN_VALUE时,xorsum & (-xorsum)就等于xorsum本身,所以这时候直接使用即可,但为什么代码中要单独处理这个情况? 可能是在这种情况下,xorsum & (-xorsum)的结果会因为溢出而不正确?或者是否在Java中,当xorsum是Integer.MIN_VALUE时,-xorsum仍然是Integer.MIN_VALUE(因为溢出),所以这时候进行&运算的结果就是xorsum本身。这时候,掩码是正确的,因为最低有效位就是最高位。所以这个条件判断可能只是为了防止溢出带来的问题? 或者可能当xorsum是Integer.MIN_VALUE的时候,xorsum & (-xorsum)的计算会得到正确的掩码,所以不需要特殊处理,但代码中作者可能为了保险起见,或者在某些情况下可能出错,所以直接使用xorsum? 比如,假设xorsum是Integer.MIN_VALUE,那么它的二进制是10000000 00000000 00000000 00000000。在Java中,-xorsum的计算是取反加1,即补码运算。原数的补码是它本身(因为是负数)。取反的话,得到01111111 ... 1111,加1得到10000000 ... 0000,所以-xorsum等于xorsum本身。这时候xorsum & (-xorsum)的结果就是xorsum。这时候,lsb的值就是xorsum本身,也就是最高位的1。这时候,用这个掩码来分割数组是正确的,因为两个单独的数字在这一位上是不同的。因此,这个条件判断是否是多余的? 可能作者考虑到当xorsum是Integer.MIN_VALUE时,-xorsum会导致溢出,从而使得xorsum & (-xorsum)的结果仍然是xorsum,这在逻辑上是正确的。所以,这个时候的条件判断是否必要? 例如,假设xorsum是Integer.MIN_VALUE,那么xorsum & (-xorsum)的结果等于xorsum,而这时候xorsum & (-xorsum)的结果确实是正确的掩码(唯一的1位),所以此时条件判断是否多余? 可能作者在这里是为了处理可能的情况,即当xorsum为Integer.MIN_VALUE时,直接使用xorsum本身作为掩码,而不用做位运算,因为在这种情况下,-xorsum会导致溢出,但位运算的结果仍然正确。或者,是否在某些情况下,当xorsum是Integer.MIN_VALUE时,xorsum & (-xorsum)的行为会有问题? 或者,可能作者在这里的考虑是,当xorsum是Integer.MIN_VALUE时,xorsum & (-xorsum)的结果还是xorsum,而其他情况下,用xorsum & (-xorsum)来得到最低有效位。所以条件判断可能只是为了避免潜在的溢出问题,但实际上在这种情况下,位运算的结果是正确的,所以条件判断可能并非必须,但作者出于谨慎添加了。 所以,这一步的目的是找到xorsum中最右侧的1的位置,作为分组的依据。例如,如果xorsum是6(二进制0110),那么xorsum & (-xorsum)得到0010,即第二个bit位。这样,数组中所有数的这个bit位为1的分为一组,剩下的为另一组。这样就能确保两个单独的数被分到不同的组中,而其他相同的数会被分到同一组,从而异或后消去。 总结来说,lsb这一步是为了找到两个单独数不同的最低有效位,从而将数组分成两组,每组各包含一个单独数,然后通过异或找出这两个数。而处理Integer.MIN_VALUE的情况可能是为了避免溢出或者确保正确性。</think>我们来逐步解析这段代码,特别是关键步骤 `int lsb = ...` 的作用。 --- ### **代码整体思路** 题目要求:**在数组中找出仅出现一次的两个数字,其余数字均出现两次。** 代码核心思路: 1. **异或性质**:相同数字异或结果为 $0$,不同数字异或结果为 $1$。因此,对所有数字异或后,最终结果 `xorsum` 等于两个目标数字的异或值。 2. **分组**:通过 `xorsum` 的某一位将数组分为两组,每组包含一个目标数字,其他数字成对出现,最终通过异或消去。 --- ### **关键代码解析** #### **1. 计算异或总和** ```java int xorsum = 0; for (int num : nums) { xorsum ^= num; ``` - **目的**:得到两个目标数字的异或值 `xorsum`。例如,若目标数字是 $a$ 和 $b$,则 `xorsum = a ^ b`。 --- #### **2. 确定最低有效位(LSB)** ```java int lsb = (xorsum == Integer.MIN_VALUE ? xorsum : xorsum & (-xorsum)); ``` - **作用**:找到 `xorsum` 中最右侧的 $1$ 的位置(即最低有效位),用于分组。 - **原理**: - `x & (-x)` 在二进制中会保留最右侧的 $1$,其余位设为 $0$。例如: - 若 $x = 6$(二进制 $0110$),则 $-x = 1010$(补码表示),$x \& (-x) = 0010$。 - **特殊情况**:当 `xorsum` 是 `Integer.MIN_VALUE`(二进制 $1000\cdots0000$),其补码表示为自身,直接取 `xorsum` 本身作为掩码。 --- #### **3. 分组异或** ```java int type1 = 0, type2 = 0; for (int num : nums) { if ((num & lsb) != 0) { type1 ^= num; } else { type2 ^= num; } } ``` - **步骤**: 1. 根据 `lsb` 将数组分为两组: - 第 $1$ 组:数字在 `lsb` 对应位为 $1$。 - 第 $2$ 组:数字在 `lsb` 对应位为 $0$。 2. 每组分别异或,结果即为两个目标数字。 --- ### **示例说明** 假设数组为 $[1, 2, 1, 3, 2, 5]$,目标数字是 $3$ 和 $5$: 1. **计算 `xorsum`**: $3$ ^ $5 = 6$(二进制 $0110$)。 2. **确定 `lsb`**: $6 \& (-6) = 0010$(二进制)。 3. **分组**: - 第 $1$ 组(`lsb` 位为 $1$):$[2, 3, 2]$ → 异或结果为 $3$。 - 第 $2$ 组(`lsb` 位为 $0$):$[1, 1, 5]$ → 异或结果为 $5$。 --- ### **总结** - **`lsb` 的作用**:通过异或结果的某一位差异,将数组分为两组,确保两个目标数字被分离。 - **特殊处理 `Integer.MIN_VALUE`**:防止补码运算溢出导致掩码错误,直接使用其本身作为掩码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值