Acwing-4645. 选数异或

文章讨论了一种优化算法,用于解决在给定数组和区间查询下,判断是否存在特定异或值配对的问题。通过预处理数组,使用哈希表存储每个数最右侧的位置,可以将时间复杂度从n^2降低到线性,从而避免TimeLimitExceeded(TLE)错误。
摘要由CSDN通过智能技术生成

 暴力解法,TLE了(呜呜呜呜)

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 1e5 + 10;

int n, m, x;
int a[N];

int main()
{
    scanf("%d%d%d", &n, &m, &x);
    for (int i = 1; i <= n; ++ i) scanf("%d", &a[i]);
    
    while (m --)
    {
        int l, r;
        scanf("%d%d", &l, &r);
        
        bool flag = false;
        for (int i = l; i <= r; ++ i)
        {
            for (int j = i + 1; j <= r; ++ j)
            {
                if ((a[i] ^ a[j]) == x)
                {
                    flag = true;
                    printf("yes\n");
                    break;
                }
            }
            if (flag) break;
        }
        
        if (!flag) printf("no\n");
    }
    
    return 0;
}

正解:(今天玩累了,明天再写解析吧,嘿嘿)

------------------------------------分割线--------------------------------------------

如何快速判断每个询问是yes或no?

异或对我们在枚举时可以枚举后一个位置(固定枚举后面这个数),这样的话每次找配对数就去左边去找,假设我们当前枚举到b了,那是否在b左边存在一个a,使得a ^ b = x,根据异或运算的性质,我们可以把a算出来,即a = x ^ b

原问题,在这个区间当中,b左边是否存在一个数与b配对 <==等价于==>   在这个区间中是否在b左边存在一个数是x ^ b

在b的左边与b配对的数可能会有很多,我们现在要找的是在给定的区间中存不存在这样的数,所以我们可以预处理一下,对于每个数预处理出来它左边离它最近的一个可以和它配对的数

设 f (i) 表示 在a[i] 左边与 a[i] 配对的最近的一个数的下标,那么我们可以通过判断 f (i) 是否在L-R之间,就可以判断是否存在与a[i] 配对的数。由于配对数右边这个数,是可以遍历整个区间的,那么整个区间内只要存在一个数和它配对的数是在一个区间里面就可以。

相当于是L <= i <= R(i为配对数右边这个数的下标),只要有L <= f(i) <= R(f(i) 为a[i] 左边与 a[i] 配对的最近的一个数的下标),就表示这个区间存在配对数,但是这样的话,L和R都是可以取遍1-n的,又是n^2级别的算法,所以我们需要优化一下。

 我们发现,如果i小于L(即i在这个区间左边的话),它对应的f(i)一定不是在这个区间里面的。

所以,L<=i<=R,是否有L<=f(i)<=R 等价于 1<=i<=R,是否有L<=f(i)<=R

由于f(i) <= i <= R,所以f(i) <= R 一定成立,

所以1<=i<=R,是否有L<=f(i)<=R 又等价于 1<=i<=R,是否有f(i) >= L

在1-R之间是否存在一个i,使得它对应的f(i)是大于等于L的

(如果在前面这些数中存在某一个数大于等于某个数的话  等价于  前面这些数中的最大值大于等于这个数)

我们可以预处理一个数组g[i],g[i] = max{f(1), f(2), ... , f(i)}

最终问题 L-R 存在配对的数对 等价于 1-R中f(i)的最大值即g[R]是否大于等于L (这一步是O(1))

g[i] 如何去算?我们知道与a[i]配对的数是a[i] ^ x,要找到a[i] 左边最靠右的值为a[i] ^ x的数,想去查找某个数的位置的话,我们可以用哈希表,本题a[i]和x都在2^20以内,所以a[i]^x也在2^20以内,所以我们可以开一个数组来存储每个数最靠右的位置,这样的话,f(i)就可以表示出来了,g[i]可以递推出来(时间复杂度是O(n)),g[i] = max{g(i - 1), f(i)}

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 100010, M = (1 << 20) + 10;

int n, m, x;
int last[M], g[N]; // last[x]记录数值x最后一次出现时的位置下标

int main()
{
    scanf("%d%d%d", &n, &m, &x);
    for (int i = 1; i <= n; ++ i)
    {
        int a;
        scanf("%d", &a);
        g[i] = max(g[i - 1], last[a ^ x]);
        last[a] = i;
    }
    
    while (m --)
    {
        int l, r;
        scanf("%d%d", &l, &r);
        if (g[r] >= l) puts("yes");
        else puts("no");
    }
    
    return 0;
}

还有一种理解方式,令dp[i]为[1, i]区间中所有数对中的最大下界,dp[r]的含义是当查询区间[l, r], 右边界为r时, 至少包含一个数对时的左边界最大值, 所以如果l小于等于这个左边界最大值, [l, r]区间内就至少有一个数对。

哈希表last[x]记录数值x最后一次出现时的位置下标dp[i]的求法是: 若a[i]⊕x最后一次出现的下标要大于dp[i-1], 则dp[i] = last[a[i]⊕x], 否则dp[i] = dp[i-1]此处重申dp[i]的含义是[1, i]区间中所有数对中的最大下界

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 100010, M = (1 << 20) + 10;

int dp[N], last[M];
int n, m, x;

int main()
{
    scanf("%d%d%d", &n, &m, &x);
    for (int i = 1; i <= n; ++ i)
    {
        int a;
        scanf("%d", &a);
        dp[i] = max(dp[i - 1], last[a ^ x]);
        last[a] = i;
    }
    
    while (m --)
    {
        int l, r;
        scanf("%d%d", &l, &r);
        if (dp[r] >= l) puts("yes");
        else puts("no");
    }
    
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青衫客36

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值