题干:
Jon fought bravely to rescue the wildlings who were attacked by the white-walkers at Hardhome. On his arrival, Sam tells him that he wants to go to Oldtown to train at the Citadel to become a maester, so he can return and take the deceased Aemon's place as maester of Castle Black. Jon agrees to Sam's proposal and Sam sets off his journey to the Citadel. However becoming a trainee at the Citadel is not a cakewalk and hence the maesters at the Citadel gave Sam a problem to test his eligibility.
Initially Sam has a list with a single element n. Then he has to perform certain operations on this list. In each operation Sam must remove any element x, such that x > 1, from the list and insert at the same position , , sequentially. He must continue with these operations until all the elements in the list are either 0 or 1.
Now the masters want the total number of 1s in the range l to r (1-indexed). Sam wants to become a maester but unfortunately he cannot solve this problem. Can you help Sam to pass the eligibility test?
Input
The first line contains three integers n, l, r (0 ≤ n < 250, 0 ≤ r - l ≤ 105, r ≥ 1, l ≥ 1) – initial element and the range l to r.
It is guaranteed that r is not greater than the length of the final list.
Output
Output the total number of 1s in the range l to r in the final sequence.
Examples
Input
7 2 5
Output
4
Input
10 3 10
Output
5
Note
Consider first example:
Elements on positions from 2-nd to 5-th in list is [1, 1, 1, 1]. The number of ones is 4.
For the second example:
Elements on positions from 3-rd to 10-th in list is [1, 1, 1, 0, 1, 0, 1, 0]. The number of ones is 5.
题目大意:
给一个数n,和一个区间[l,r] (r-l<1e5,n<2^50),每次需要把序列中大于1的数字分成(n/2,n%2,n/2)(其中n/2是向下取整),直到所有数变成0或1,问[l,r]区间内有多少个1?
解题报告:
想着先把数字分好,然后再o(n)区间查询一下?有点麻烦,,其实分数字的过程中,答案就可以带出来了。
AC代码:
#include
#define ll long long
#define pb push_back
#define pm make_pair
#define fi first
#define se second
using namespace std;
ll l,r;
ll dfs(ll n,ll curl,ll curr) {
if(curl > r || curr < l) return 0 ;
if(n == 1) return 1;
ll res = 0;
ll mid = (curl+curr)/2;
if(n % 2 == 1 && (mid<=r && mid>=l)) res++;
return res + dfs(n>>1,curl,mid-1) + dfs(n>>1,mid+1,curr);
}
int main()
{
ll n,x,len = 1;
cin>>n>>l>>r;
x = n;
while(x > 1) {
len = len*2+1;
x>>=1;
}
printf("%lld\n",dfs(n,1,len));
return 0 ;
}
总结:
刚开始写代码的时候两个地方写萎了
第一处:两个出口写颠倒了,想想也是不对啊,肯定是首先要在区间内部啊!!这是毋庸置疑的啊!!
第二处:if(n % 2 == 1) res++;
至于为什么没有像线段树一样写上如果两者都在所求区间内部的话 单独返回一个值,这是因为我们提前把len算好了,所以最后分出的答案一定是0或者1,可以直接通过那个n==1来防止无限递归。然后就是对于0的处理,只需要那个res那里处理一下就可以了,因为只有中间的值才会出现等于0的情况,两边的值是不会出现为0的情况的,因为是除2操作嘛!!枚举几个终点可能值,就可以简单证明了,3/2=1,,,2/2=1,,,,1的话就是出口了,所以拆数的时候不会在两边有0出现,只可能是中间会有0出现。
所以其实这题我们也可以直接模拟一下过程,找找会有几个mid值是偶数,那么才会有0出现,假设求出0的数量是cnt0,那么结果就是r-l-cnt0。但是其实还是要模拟线段树build的过程的、、、、因为还要找和l,r的大小关系。
另附一个网络的二分思路的代码:(还未看)
#include
using namespace std;
typedef long long ll;
ll n, l, r, s = 1, ans;
void solve(ll a, ll b, ll l, ll r, ll d) { //二分的思想
if ( a > b || l > r ) return;
ll mid = (a+b)/2;
if ( r < mid )solve(a,mid-1,l,r,d/2);
else if( mid < l )solve(mid+1,b,l,r,d/2);
else {
ans += d%2;
solve(a,mid-1,l,mid-1,d/2);
solve(mid+1,b,mid+1,r,d/2);
}
}
int main() {
cin >> n >> l >> r;
long long p = n;
while ( p >= 2 ) {
p /= 2;
s = s*2+1;
}
solve(1,s,l,r,n);
cout << ans << endl;
return 0;
}