Aninteresting game HDU - 5975 (树状数组lowbit深入理解)

Aninteresting game

 题目链接:HDU - 5975 

题意:有1~n个数,将i放入集合中时同时放入了[i-lowbit(i)+1, i-1]区间的数,每向集合中放入一个数就耗费一点体力,所以,向集合中放入i时就是同时放入[i-lowbit(i)+1, i]区间的数,共i-(lowbit(i)+1)+1=lowbit(i)个数,消耗的体力就是lowbit(i);问将区间[l, r]的数全部放入集合中需要耗费的体力,及数x在被放入几次;

解析:先看第一问:将区间[l, r]的数全部放入集合中需要耗费的体力就是l~r的lowbit值之和;由于数据太大,直接求[l, r]的所有lowbit再加起来求和会超时(虽然这是O(n)的算法);那么就想能不能直接求lowbit的前缀和这样不就简单多了,问题是如何求前缀和;在此之前先来看一下lowbit求出来的到底是什么;

lowbit(i)求出来的是i的二进制由左向右第一个1出现的位置构成的十进制数;如:

6(十进制)=110(二进制), lowbit(6(十进制))=10(二进制)=2(十进制);

那么前n个数的lowbit前缀和就相当于求前n个数中:1第一次出现在第0位的次数*2^0=1   +    1第一次出现在第1位的次数*2^1=2    +    1第一次出现在第2位的次数*2^2=4    +    1第一次出现在第3位的次数*2^3=8    +    ······

前n个数的二进制中1第一次出现在第i位的次数怎么算?

二进制中如果1第一次出现在第i位,那么该数一定是2^i的倍数且不是2^(i-1)的倍数;

那么前n个数中2^i的倍数的个数减去2^(i+1)的倍数的个数,就是1第一次出现在第i位的次数,那么我们就可以O(logn)的算出前n个数的lowbit和;

再看第二问:x被放入集合中几次?只要x在[i-lowbit(i)+1, i]区间就会被放入一次;

在树状数组中以r为父节点的区间范围是多少?[r-lowbit[i]+1, r];第二问就简化成了x的父节点有几个,就是x到根节点经过的点的个数,如何找根节点?和树状数组的更新是相同的,就是一直r+=lowbit(r),直到r>=n;运算了几次就有几个x

#include <bits/stdc++.h>
using namespace std;
long long lowbit(long long x){
	return x&(-x);
}
long long cal(long long n){
	long long ans=0;
	for(long long p=1; p<=n; p<<=1){
		ans+=(n/p-n/(p<<1))*p;
	}
	return ans;
}
int sum(long long x, long long n){
	int ans=0;
	while(x<=n){
		x+=lowbit(x);
		ans++;
	}
	return ans;
}
int main(){
	long long n, q;
	while(~scanf("%lld%lld", &n, &q)){
		int op;
		while(q--){
			scanf("%d", &op);
			if(op==1){
				long long l, r;
				scanf("%lld%lld", &l ,&r);
				printf("%lld\n", cal(r)-cal(l-1));
			}
			else{
				long long x;
				scanf("%lld", &x);
				printf("%d\n", sum(x, n));
			}
		}
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/sirius_han/article/details/81151220