P.S. 树状数组之前认为难以理解,但是看了这个之后,恍然大悟,以下题目来自洛谷
先三连+%up为敬
问题P3374:给你n个数,要进行k次单点修改和区间查询的操作
给出一个表来对比一下暴力和树状数组:
做法 | 修改复杂度 | 查询复杂度 |
---|---|---|
朴素暴力 | (O(1)) | (O(n×k)) |
树状数组 | (O(logn)) | (O(logn)) |
借百度的图讲一下:
令这棵树的结点编号为(f_1),(f_2)...(f_n),数组编号为(a_1),(a_2)...(a_n)
所以:
(f_1)=(a_1)
(f_2)=(a_1)+(a_2)
(f_3)=(a_3)
(f_4)=(a_1)+(a_2)+(a_3)+(a_4)
……
发现性质:设节点编号为(i),那么这个节点管辖的区间为(2^k)(其中(k)为(i)二进制末尾0的个数)个元素
因为这个区间最后一个元素必然为(a_i),
比如说,我们要求(f_4),可以得到
(f_4)=(a_1)+(a_2)+(a_3)
把4转换为2进制,((4)_{10})=((100)_2)
然后就是区间查询
假如我们要查询(a_1)+(a_2)+...+(a_{13})的值就是要查询这几个区间的和的:
所以(a_1)+(a_2)+...+(a_{13})=(f_8)+(f_{12})+(f_{13}),只用把3个值加起来就行了
((13)_{10})=((1101)_2)
操作 | 1 | 2 | 3 |
---|---|---|---|
抹去二进制的最后一个1 | ((1101)_2) | ((1100)_2) | ((1000)_2) |
转化成十进制 | 13 | 12 | 8 |
管辖的范围 | (2^0) | (2^2) | (2^3) |
神奇的发现:(2^0)+(2^2)+(2^3)恰好等于13
由于每次最多抹掉(log(n))个0,也就是最多有(log(n))次查询,复杂度为(O(logn))
接下来是单点修改
假如要修改的节点编号为(i)
由于(f)数组储存的是前缀和,所以要修改所有包含(i)的区间
当(i)=5时,就是要修改这几个区间的值:
不难发现,对于单点修改的操作,其实就是把区间查询的操作倒了过来
操作 | 1 | 2 | 3 | 4 |
---|---|---|---|---|
在二进制的最后一个1的位置加1 | ((101)_2) | ((110)_2) | ((1000)_2) | ((10000)_2) |
转化成十进制 | 5 | 6 | 8 | 16 |
管辖的范围 | (2^0) | (2^1) | (2^3) | (2^4) |
这里就不再阐述了
总结:查询就是在最后一个1的位置减1,修改就是在最后一个1的位置加1
那怎么得到二进制的最后一个1在哪里
(largeoxed{lowbit})操作
inline int lowbit(int x) { return x & -x; }
这段代码是什么含义呢?
先谈谈二进制中如何表示负数:
一个正数的相反数用二进制表示就是这个数按位取反再+1
比如说((1100)_2)的负数用二进制表示就是((0011)_2)+1,也就是((0100)_2)
(1100)&(0100)=(0100)
那为什么(lowbit)能求二进制最末位的1在哪?
我们要求的这个数末尾连续的0取反之后会全部变成1(就是二进制下这个数的相反数减一之后末尾的1与这个数末尾的0的数量相同)
在负数中把减去的1加上之后,末尾的1会全部变成0,而最末尾的0会变成1(加法)
以12为例子,给张图理解一下:
放上核心代码:
int n, f[100003];
inline int lowbit(int x) { return x & -x; }
inline int query(int x)//查询
{
int ans = 0;
while (x)
ans += f[x], x -= lowbit(x);
return ans;
}
inline void add(int x, int val)//修改
{
while (x <= n)
f[x] += val, x += lowbit(x);
}
P.S.求([l,r])区间和就是求(query(r)-query(l-1))的值
问题P3368区间修改和单点查询
如果上面宁已经看懂了,接下来的内容就很简单了
区间修改,其实用到了差分的思想
之前的(f_i)是指(1)~(i)的和(及前缀和),这里(f)储存的是差分数组
还是拿百度的图举例子
假如此时我们要把区间(a[1,6])都加上值(val)应该如何操作呢?
利用差分的思想,在1的位置上加上val,在6+1的位置上减去val
这样查询(f[1,6])时,就正好加上了值,查询到大于6的时候,先加了(val),又减去了(val)正好抵消(qwq)
然后给出(color{limegreen}{AC})代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,m,opt,l,r,k,pre,now,f[500005];
inline ll read(){
ll x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
inline ll lowbit(ll x){return x&-x;}
inline ll query(ll x){ll ans=0;while(x) ans+=f[x],x-=lowbit(x);return ans;}
inline void add(ll x,ll val){while(x<=n) f[x]+=val,x+=lowbit(x);}
int main(){
n=read(),m=read();
for(ll i=1;i<=n;++i){now=read();add(i,now-pre);pre=now;}
while(m--){
opt=read();
if(opt==1) {l=read(),r=read(),k=read();add(l,k),add(r+1,-k);}
else k=read(),printf("%lld
",query(k));
}
return 0;
}
欢迎提出建议(qwq)
完结撒花(qwq)