题目:https://codeforces.com/contest/1245/problem/F
题意:
有t次询问(t<=100)
每次给2个数a,b(0<=a,b<=1e9)
求 a,b区间中(包括a,b)有多少对数(i,j)满足: i+j=i^j
首先i+j=i^j等价于 i&j==0
然后假设solve(i,j)表示 : 在 [i,j) (注意这里是半开区间)中有多少对数符合上述条件。
有结论:
1.solve(0,j)=2*j-1+solve(1,j)
因为数字0和任何数都满足 0+i=0^j ,能组成的对数为 (0,0),(0,1)……(0,j-1) ,(1,0),(2,0)……,(j-1,0) 一共2*j-1对
2. solve(2*i,2*j) = 3*solve(i,j)
因为乘二相当于二进制左移一位,假设原来的(a,b)满足a&b==0,则在solve(2*i,2*j)中有3对满足条件,分别为(2*a,2*b),(2*a,2*b+1),(2*a+1,2*b)
注意因为solve表示的i,j区间为半封闭式[i,j), 因此i,j乘2之后所有的原来符合条件的对子都会产生3对满足条件的对子。
显然有了上面两条式子还不够,假如i,j为奇数的时候怎么办呢
就把他们变成偶数
设g(mx,flag)表示: 满足0<=y<mx 且 flag&y==0 的y的个数
本着将i,j区间缩小的原则,于是有:
3.solve(2*i-1 , 2*j) = solve(2*i,2*j) + 2*( g( 2*j , 2*i-1) - g( 2*i-1 , 2*i-1) )
4.solve(2*i , 2*j+1) =solve(2*i,2*j) + 2*( g( 2*j+1 , 2*j)- g( 2*i , 2*j ) )
关键是如何求g(mx,flag) 呢?
对于mx来说,可以分段求结果,也就是说,将mx看成二进制,每次求 (mx-lowbit(mx) , mx]中和flag进行与操作后结果为0的个数
例如
flag=110110110 (二进制表示 )
mx= 100100100 (二进制表示)
第一步:
求属于区间(100100000 , 100100100] 并且和flag进行与操作结果为0的个数
首先先统计flag二进制中从 末位 到 lowbit(mx) - 1 这几个位数中0的个数 zero
然后mx^=lowbit(mx) ,此时mx=100100000
假如此时mx&flag==0的话,就说明这个区间中可能存在和flag进行与操作结果为0的数,那么将(1<<zero)累加到结果中去
第二步
求属于区间(100000000,100100000]并且和flag进行与操作结果为0的个数
……
以此类推
……
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
using namespace std;
const int INF = 0x3f3f3f3f;
ll g(ll mx, ll flag) { //mx:0~mx flag:flag&x==0
ll res = 0, zero = 0;
for (ll i = 1; i <= mx; i <<= 1LL) {
if (mx & i) {
mx ^= i;
if (!(mx&flag))
res += (1LL << zero);
}
if (!(flag & i)) {
zero++;
}
}
return res;
}
ll solve(ll l, ll r) {
if (l == r) {
return 0;
}
if (l == 0) {
return 2 * r - 1 + solve(1, r);
}
if (l & 1LL) {
return solve(l + 1, r) + 2LL * (g(r, l) - g(l, l));
}
else if (r & 1LL) {
return solve(l, r - 1) + 2LL * (g(r, r - 1) - g(l, r - 1));
}
else {
return 3 * solve(l >> 1LL, r >> 1LL);
}
}
int main() {
int t; cin >> t;
ll l, r;
while (t--) {
cin >> l >> r;
cout << solve(l, r + 1) << endl;
}
return 0;
}