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

Posted hznudreamer

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 }

 

以上是关于P6025 线段树(规律+模拟+位运算)的主要内容,如果未能解决你的问题,请参考以下文章

线段树与位运算

完全二叉树编号关于位运算的规律题——222. 完全二叉树的节点个数

二进制拆分线段树

[拆位线段树]RMQ

HDU 6186 CS Course前后缀位运算枚举/线段树

Codeforces Round #590 (Div. 3) D. Distinct Characters Queries(线段树, 位运算)