【每日一题】小AA的数列

小AA的数列

https://ac.nowcoder.com/acm/problem/14414

痛苦,悲伤,贼难orz


题目

题目描述:
小AA找到了一个数列,她想要知道这个数列中所有长度为偶数的区间异或和之和 。
后来她发现这个问题太简单了,于是她加了一个限制,要求区间长度在[L,R]之间,
然后她就不会了。。。
请你告诉她问题的答案。

输入描述:
第一行三个数 n, L, R(n<=105,1<=L<=R<=n)
第二行n个数表示这个数列。(ai<=109)

输出描述:
输出一行表示答案,由于答案可能很大,请输出答案模109+7的值。


解析

这么久以来,我一看到异或和的题目,或者是按位求贡献的题目,我就不会。。。我这次看了一天总算懂了一点orzzzz

首先我们看一下题目
  1. 首先当我们看到异或和这种题目,就一定能想到缀和
  2. 然后是求异或和之和,既然是求和,我们也能想到按位求贡献
  3. 然后另外这道题就是难在:长度为偶数长度在L~R之间


首先我们讲一下按位求贡献
  1. 首先讲到按位求贡献的原理:
    如果一堆数求异或,用二进制也就是每一位求异或,也就是一堆0和1求异或,0对答案是没有任何贡献的,只有1有贡献
    并且1的数量为奇数,异或值为1。如果1的数量为偶数,异或值为0因为异或相同的为0,所以两个1就会抵消)。
  2. 所以在这一堆数求异或之后,结果每一位留下的数,组成十进制之后,就是这一堆数的异或值


然后是前缀和
  1. 我们用前缀和来储存前面所有数字的每一位上1的数量。(我们就是用这个方法可以O(1)地查到一段区间的异或值)
  2. 所以这里我们的前缀和数组为:
    int sum[MAX][30];//sum的前i项(前i个数),二进制第j位上从info[1]~info[n]上的1的数量(对二进制每一位的前缀和)


根据这道题的特性,我们要求前缀和之
  1. 所以我们这里要知道有多少个前缀和(统计前缀和的个数)
  2. 也就是知道我们这里有多少个可以用来求和的1(每一个前缀和都是用来保存当前的异或值,所以这里也就是在求异或和之和)。
  3. 这里我们就可以用一个新的数组来统计异或和中每一位的数量(没错,统计每一位,因为十进制的异或和依旧可以转换成二进制按位求值):
    int cnt[MAX][30][2];//第一维为数字表示前i项,然后是二进制第j位,0/1表示前j位sum为奇/偶的个数
  4. 这里要讲的就是奇偶的个数了,因为题目要求了,只能是偶数长的区间,所以在起点相同的情况下,偶数长一定是终点全奇数点或全偶数点的。


然后就要考虑长度为偶数的情况了:
  1. 因为长度为偶数,也就是说,每隔一个,我们才能进行一次计算。那我们自然而然也就会想到位置的奇偶性
  2. 也就是我们上面在统计前缀和之和的时候所考虑到的奇偶点的情况了。
  3. 然后我们就可以按照我们上面讲的来初始化我们的sum和cnt数组了。


接下来就是考虑L~R的情况了,这个其实就是计算的问题,我们就在计算答案的时候判断就好了:
  1. 首先我们要用到外层循环遍历左端点,并确定右端点所在的区间范围的位置(用left和right标记区间两端):
    int left = i + L - 1, right = min(n, i + R - 1);//区间长为L~R,计算出区间位置
    //注意一下,right可能越界,越界就自然的选n
  2. 这里要注意特判,区间长度可能为奇数,所以要排除这种情况
    if ((left - i + 1) & 1) left++;
    if ((right - i + 1) & 1) right--;
    //强制使区间长度变为偶数,左端点只能向右,右端点只能向左(不能跑到区间外面去了)
    //left - i + 1算出来就是区间长度,所以就是区间长度&1
    //这里不用L和R进行判断是因为,右端点可能越界,然后选了n做右端点
  3. 自然而然,left和right缩小范围之后,left可能会比right要大,就不符合常理了,就不计这种情况,continue就行。
  4. 否则,我们就可以正常的进行计算:计算方式就是简单的对每一位求出在这个区间里面1的数量
    (利用cnt数组的差值就可知道,我们上面隔一个进行统计就是为了这里),加起来就是异或和之和了。
  5. ————————————————————skr————————————————————
  6. 然后我们分步讲解一下:
  7. 首先,我们知道cnt里面是统计的奇数和偶数的sum的个数的,用哪个呢?就要考虑我们的起点了:我们要偶数长度,起点终点的奇偶性当然就不一样:
    int v = (sum[i - 1][j] & 1) ^ 1;
    //v表示右端点,为了使长度为偶数长,起点(sum[i - 1][j] & 1)和终点的奇偶性不一样
    //数字取反我们用^1,C/C++用!也是一样的。
  8. 然后我们就要用右端点和左端点取差值了:
    ll tot = cnt[right][j][v] - cnt[left - 2][j][v];//计算右端点和左端点之间的差值(sum异或和为1的个数)
  9. 求完了数量再给他把这些东西给他放到答案的每一位上去就好了:
    ans = (ans + (1 << j) * tot % MOD) % MOD;
  10. 如此循环统计。


总算到了打代码了:
  1. 首先自然就是输入我们的数据。
  2. 然后用这些数据将sum和cnt数组按照上面的方式进行初始化了。
  3. 接下来我们就按照上面讲的异或和统计将答案统计累加出来
  4. 输出答案就好。
  5. 看我代码吧。。。代码还是很详细的orzzzz


AC代码

#include <iostream>
using namespace std;
typedef long long ll;
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
//代码预处理区

const int MOD = 1e9 + 7;
const int MAX = 1e5 + 7;
int n, L, R;
int sum[MAX][30];//sum的前i项(前i个数),二进制第j位上从info[1]~info[n]上的1的数量(对二进制每一位的前缀和)
int cnt[MAX][30][2];//第一维为数字表示前i项,然后是二进制第j位,0/1表示前j位sum为奇/偶的个数
//全局变量区

int main() {
    IOS;
    cin >> n >> L >> R;
    for (int i = 1; i <= n; i++) {
        int info; cin >> info;//输入数据
        for (int j = 29; ~j; --j) {
            sum[i][j] = sum[i - 1][j] + ((info >> j) & 1);
            //sum第二维(二进制位)的累加,(info[i] >> j) & 1取出二进制第j位
            if (i != 1) {
                cnt[i][j][0] = cnt[i - 2][j][0];
                cnt[i][j][1] = cnt[i - 2][j][1];
            }
            //本位先继承之前位的东西,之所以是i - 2是因为区间长为偶数
            cnt[i][j][sum[i][j] & 1]++;
            //再加上本位的情况,sum[i][j] & 1就是判断当前sum奇偶
        }
    }
    //数组初始化

    int ans = 0;
    for (int i = 1; i <= n; i++) {
        //外层循环枚举左端点,i为左端点
        int left = i + L - 1, right = min(n, i + R - 1);//区间长为L~R,计算出区间位置
        if ((left - i + 1) & 1) left++;
        if ((right - i + 1) & 1) right--;
        //强制使区间长度变为偶数,左端点只能向右,右端点只能向左(不能跑到区间外面去了)
        //left - i + 1算出来就是区间长度,所以就是区间长度&1
                //这里不用L和R进行判断是因为,右端点可能越界,然后选了n做右端点

        if (left > right) continue;//特判缩小范围后越界的情况

        for (int j = 29; ~j; j--) {
            int v = (sum[i - 1][j] & 1) ^ 1;
            //v表示右端点,为了使长度为偶数长,起点(sum[i - 1][j] & 1)和终点的奇偶性不一样
            ll tot = cnt[right][j][v] - cnt[left - 2][j][v];
                  //计算右端点和左端点之间的差值(sum异或和为1的个数)
            ans = (ans + (1 << j) * tot % MOD) % MOD;
        }
    }
    cout << ans << endl;
    return 0;
}
//函数区
每日一题 文章被收录于专栏

憨憨的专栏

全部评论

相关推荐

04-12 21:52
南开大学 Java
鼠鼠有点摆,去年边学着没敢投简历,没实习。从1月到现在总共面了五次,四次字节的日常(HR打电话约面试才敢去的),然后一次腾讯的暑期,都是一面挂,其他则是没给面。暑期的岗,4.2才开始海投,前面想着等字节第四次一面后再投,结果挂,而且感觉投晚了。字节投了11个,9个简历挂,剩下2个没动静。阿里全都简历挂,剩下的在&quot;投递简历&quot;。腾讯给了一次面。然后其他大中厂、手机厂什么的都是做完测评or笔试就没下文,打开几个看也是终止流程,感觉剩下的也应该是简历挂了。感觉是简历的原因?项目部分,几次面试,感觉面试官主要就拷问过秒杀这一个点。自己说的时候会尝试把sse那条说成亮点,但除了腾讯面试官问过一下这整个点在业务方面对用户有什么用之类的问题外,其他最多只是问一下sse八股...感觉也许不是很让面试官感兴趣。这个短链接也是无人问津,就被问过一回雪花算法的设计。也许我该拿点评改改,然后再在网上找一个什么项目,凑两个,而不是用自己现在这两个项目?或者是点评改改放前面,然后原本第一个项目,把秒杀抽掉,剩下的想办法从网上火的RAG项目里移植点亮点,或者直接就用网上的RAG项目?感觉我主要还是偏向后端开发,但是感觉如果除开点评,再拿一个项目,想不到有什么自己能掌控且跟点评不重的。然后鼠鼠之前主要的问题是担心面试让打开项目演示,然后就一直花时间在用AI整第一个项目,第二个项目都没时间整,第四次面试之前还因为太害怕被认为不熟悉项目,跟AI一起把简历的说辞做了大幅度弱化,然后暑期都是拿弱化后的简历投的,感觉是不是看上去太没有吸引力就直接给简历挂了。(图1是弱化后的,图2是弱化前的,但之前3月初投了几家好像也是简历挂。)而且因为3月花了很多时间整在跟AI整代码,导致八股和算法都没怎么看,算法之前有跟灵神题单刷一些,还算入门,但是八股只看了一些基本的,可能面试的时候只答得上来60-70%,而且表述有些混乱,都是想到哪说到哪;前面几回面试基本上都有大板块的基础八股没答出来,比如RedisZ&nbsp;Set数据结构,MQ延时消息、可靠性保证,JVM内存分配的过程、GC&nbsp;roots,JUC锁,设计模式。现在有点不知道该怎么办。求大佬们给点简历修改建议或者面试准备建议,不胜感激!
何时能不做牛马:简历每个点之间的间距可以缩一下。几乎没遇到过要演示项目的情况,即使万一遇上了你也可以说部署在其他电脑上本地没代码。nku不应该简历挂吧?抓紧背背八股练练表达,不要放弃,五六月份找到也不晚(不然还得提前入职
应届生简历当中,HR最关...
点赞 评论 收藏
分享
找工作勤劳小蜜蜂:自我描述部分太差,完全看不出想从事什么行业什么岗位,也看不出想在哪个地区发展,这样 会让HR很犹豫,从而把你简历否决掉。现在企业都很注重员工稳定性和专注性,特别对于热爱本行业的员工。 你实习的工作又太传统的it开发(老旧),这部分公司已经趋于被淘汰,新兴的互联网服务业,比如物流,电商,新传媒,游戏开发和传统的It开发有天然区别。不是说传统It开发不行,而是就业岗位太少,基本趋于饱和,很多老骨头还能坚持,不需要新血液。 工作区域(比如长三角,珠三角,成渝)等也是HR考虑的因素之一,也是要你有个坚定的决心。否则去几天,人跑了,HR会被用人单位骂死。
点赞 评论 收藏
分享
评论
9
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务