消除二进制中最后一个位1,利用n &= (n - 1)
就可以做到。然后看一下消了几次n
变成0,次数就是答案。
n &= (n - 1)
具体的操作是把最后一位1变成0,后面的0变成1,与运算一下,最后一位1就消掉了。
{ n &= (n - 1) :即 n = n & ( n - 1 ); 也就是说 只有当n与(n-1) 都为1时 n为1;
[&:与运算] }
以下稍微变动自大佬博客https://www.cnblogs.com/hsd-/p/6139376.html
树状数组,看名字就知道是树状的数组;(如下图二叉树所示)
叶子结点代表数组A[1]~A[8];
通过变形,可将图形转化为:
现在定义每一列的顶端节点C[ ]数组;
即为下图所示:
C[i]代表子树的叶子结点的权值之(和或差或积等其他),//这里以求和举例;
如图可以知道:
C[1] = A[1];
C[2] = A[1]+A[2];
C[3] = A[3];
C[4] = A[1]+A[2]+A[3]+A[4];
C[5] = A[5];
C[6] = A[5]+A[6];
C[7] = A[7];
C[8] =A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7]+A[8];
下面观察如下图
将C[ ]数组的结点号转化为二进制
1 = (001) C[1] = A[1];
2 = (010) C[2] = A[1]+A[2];
3 = (011) C[3] = A[3];
4 = (100) C[4] = A[1]+A[2]+A[3]+A[4];
5 = (101) C[5] = A[5];
6 = (110) C[6] = A[5]+A[6];
7 = (111) C[7] = A[7];
8 = (1000) C[8] = A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7]+A[8];
对比式子可以发现 C[i] = A[i-2^k+1]+A[i-2^k+2]+......+A[i]; (K为i的二进制中从最低位到高位连续零的长度)
当i = 8时,k = 3;带入验证正确;
现在引入lowbit (x)
lowbit (x) 就是取出x的最低位1 即为:lowbit(x) = 2^k ;
例:假设 x = 6; lowbit(6) = 2^1=2; (因为6的二进制数110从最低位到高位连续零的长度为1)
2化为二进制数就是数字6的二进制数的最低位1的位置。
lowbit 代码:
int lowbit(int t)
{
return t&(-t);
}
//-t 代表t的负数, 计算机中负数使用对应正数的b补码来表示
//例如:
//t = 6 = (0110) 此时 k = 1
//-t = -6 = (1001+1) = (1010)
//t&(-t) = (0010) = 2 = 2^1
C[i]=A[i-2^k+1]+A[i-2^k+2]+......A[i];
C[i]=A[i-lowbit(i)+1]+A[i-lowbit(i)+2]+......A[i];
区间查询(从上往下):
利用C[i]数组,求A数组中前i项的和
例如,
i= 7;
sum[7] = A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7];前i项和
C[4] = A[1]+A[2]+A[3]+A[4];
C[6] = A[5]+A[6];
C[7] = A[7];
所以可以推出:sum[7] = C[4]+C[6]+C[7];
序号写为二进制为:sum[(111)] = C[(100)]+C[(110)]+C[(111)];
i = 5;
sum = A[1]+A[2]+A[3]+A[4]+A[5]; 前i项和;
C[4] = A[1]+A[2]+A[3]+A[4];
C[5] = A[5];
所以可以推出:sum[5] = C[4]+C[5];
序号写为二进制为:sum[(101)] = C[(100)]+C[(101)];
区间查询代码:
int getsum(int x)
{
int ans=0;
for(int i=x;i>0;i-=lowbit(i))
ans+=C[i];
return ans;
}
对于i=7 进行演示
7(111) ans+=C[7]
lowbit(7)=001 7-lowbit(7)=6(110) ans+=C[6]
lowbit(6)=010 6-lowbit(6)=4(100) ans+=C[4]
lowbit(4)=100 4-lowbit(4)=0(000)
对于i=5 进行演示
5(101) ans+=C[5]
lowbit(5)=001 5-lowbit(5)=4(100) ans+=C[4]
lowbit(4)=100 4-lowbit(4)=0(000)
单点更新(从下往上);
当我们修改A[ ]数组中的某一个值时,应该如何更新C[ ] 数组
回想区间查询的过程,结合代码分析:
(更新过程是查询过程的逆向)
void add(int x, int y)
{
for(int i=x; i<=n; i=i+lowbit(i))
tree[i]=tree[i]+y;
}
如图:更新A[1]时,需要向上更新C[1], C[2], C[4], C[8]
C[1], C[2], C[4], C[8] 写为二进制分别为:
C[(001)], C[(010)], C[(100)], C[(1000)]
1(001) C[1]=C[1]+A[1]
lowbit(1)=001 1+lowbit(1)=2(010) C[1]=C[1]+A[1]
lowbit(2)=010 2+lowbit(2)=4(100) C[2]=C[2]+A[1]
lowbit(4)=100 4+lowbit(4)=8(1000) C[8]=C[8]+A[1]
----------------------------------------------------------------------------我是分割线-----------------------------------------------------------------------------
下面 写个简单题的 题解帮助更好理解和应用
首先 题意
就是 N个营地里的人 在随时变化(增加或减少),
Add A B 命令:第A个营地里增加了B个人
Sub A B 命令:第A个营地里减少了B个人
Query A B 命令:计算从第A个到第B个营地里的总人数
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
int tree[50001];
int N;
//提取最低位的1
int lowbit(int t)
{
return t&(-t);
}
//单点更新(第i个营地开始更新,更新数为y)
void add(int x, int y)
{
for(int i=x; i<=N; i+=lowbit(i))//从i=x开始 通过+lowbit 直到i不满足<=N 即把树更新完。
tree[i]+=y;
}
//区间查询
int getsum(int x)
{
int ans = 0;
for(int i=x; i>0; i-=lowbit(i))
ans+=tree[i];
return ans;
}
int main()
{
int T, x, y;
int cases=1;
char s[10];
scanf("%d",&T);//输入案例数;
while(T--)
{
scanf("%d",&N);//输入营地数
memset(tree, 0, sizeof(tree));//数组初始化;
printf("Case %d:\n",cases++);
for(int i=1; i<=N; i++)
{
scanf("%d",&x);//输入每个营地开始时的人数;
add(i, x);//并且将一开始时的人数放入树状数组中;
}
while(scanf("%s",s)&&s[0]!='E')//当输入字符串第一个不为E即操作命令并没有结束时;
{
scanf("%d %d",&x, &y);//输入对第x个营地,做出的s操作,改变y的人数;
//如果输入字符串为Add 即第x个营地 增加y个人
if(s[0]=='A')
{
add(x,y);
}
//如果输入字符串为Sub 即第x个营地 减少y个人
else if(s[0]=='S')
{
add(x,-y);
}
//如果输入字符串为Query 即计算从第x到第y个营地的总人数
else if(s[0]=='Q')
{
int ans = getsum(y)-getsum(x-1);
printf("%d\n",ans);
}
}
}
return 0;
}