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 }