Codeforces Round #597 (Div. 2) F. Daniel and Spring Cleaning

题目: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;
}

 

 

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值