• P6025 线段树(规律+模拟+位运算)


    题意:a[i]代表维护1~i区间的线段树建树后数组的最大下标,有1e15范围内的l和r,求a[l]一直异或到a[r]的值。

    分析:a[l]异或到a[r]采用从(1异或到a[l-1])异或(1异或到a[r])求出,所以只需要算1异或到a[i]即可。具体原理如何得到一个线段树数组最大下标的值还没有搞懂,但是通过朴素建树代码打表可以发现这个数值是存在规律的,并且是一种二进制的规律,(下面的元素都是二进制表示)除了第一个元素为1第二个元素为11以外,从第三个元素开始存在规律,打表后用bitset输出二进制可以发现,维护1~4的线段树最大下标是111,维护1~8的线段树最大下标是1111,维护1~16的线段树最大下标是11111,以此类推,并且维护1~3的线段树下标是101,维护1~5的线段树下标是1001,维护1~9的线段树的下标是10001,以此类推,至于中间的数,6到7之间有2个1101,随后紧接着的8是1111,10到15之间有2个11001,4个11101,随后紧接着的16是11111,以此类推,可以发现,中间那些2的某某次方的数在异或之后全部变成0,只需要计算头尾元素的异或值即可,这样一来可以在log的复杂度内计算出每一个2^k(k是正整数)的异或值,我们这里找<=当前要求的a[i]的最大2^k,然后把它的亦或值放到贡献里,剩下的部分(记为remind)我们可以发现从2^k的下一个元素开始,第一个元素必定是100…(此处若干个0)…001,然后是2个110……001,然后是4个111……001,然后是8个……,以此类推直到最后一个是111……111,因为第一个元素是最特殊的,所以我们先然之前算出来的贡献异或一下第一个元素,然后让remind个数-1,然后让剩下的数只要能减就依次减去2、4、8……,因为是偶数个数异或,所以异或出来的值还是0,同时我们更新当前异或到的元素的值(记为num),直到remind不能再减了,这个时候如果remind为奇数,说明异或了奇数次的num,所以剩下的值还是num,我们只要把之前算出来的二进制贡献异或一下num就好了,如果remind为偶数,那说明异或了偶数次,值为0,那只要输出之前的二进制贡献就好了。

     1 #include <bits/stdc++.h>
     2 #define int long long
     3 typedef long long ll;
     4 using namespace std;
     5 bitset <8> bit;
     6 int f(int x) {
     7     if (x <= 2) return x;
     8     int base = 0;
     9     int b;
    10     for (b = 1; (1ll << b) <= x; b++);
    11     b--;
    12     if (x < 4) base = 2;
    13     else
    14     for (int i = 1; i < b; i++) {
    15         if (i == 1) {
    16             if (b & 1) base ^= 1ll << i;
    17         } else {
    18             if (b & 1) {
    19                 if (~i & 1) base ^= 1ll << i;
    20             } else {
    21                 if (i & 1) base ^= 1ll << i;
    22             }
    23         }
    24     }
    25     int val = 1ll << b;
    26     if (x == val) return base;
    27     int remind = x - val;
    28     base ^= ((1ll << (b+1)) + 1ll);
    29     bit = ((1ll << (b+1)) + 1ll);
    30     remind--;
    31     if (remind == 0) return base;
    32     int cnt = 1, num = ((1ll << (b+1)) + 1ll) ^ (1ll << (b));
    33     while (remind >= (1ll<<cnt)) remind-=(1ll<<cnt),num ^= (1ll<<(b-cnt)),cnt++;
    34     if (remind & 1) base ^= num;
    35     return base;
    36 }
    37 int l,r;
    38 signed main(){
    39     cin >> l >> r;
    40     cout << (f(r) ^ f(l-1)) << endl;
    41     return 0;
    42 }
  • 相关阅读:
    快速找到由程序员到CTO发展道路上的问路石
    从大师身上反思
    你真的了解企业虚拟化吗?
    “驱网核心技术丛书”创作团队访谈
    程序员到CTO需要准备什么
    深入搜索引擎的关键——索引
    程序员到CTO必须注意的几个关键点
    微软全球MVP教你如何规划程序人生
    “碟中碟”虚拟光驱软件开发者——万春 读《寒江独钓——Windows内核安全编程 》有感
    常用jar包之commonscollection使用
  • 原文地址:https://www.cnblogs.com/hznudreamer/p/12791022.html
Copyright © 2020-2023  润新知