单点修改、区间求和
操作1: 格式:1 x k 含义:将第x个数加上k
操作2: 格式:2 x y 含义:输出区间[x,y]内每个数的和
题目来源:LibreOj130
#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 100;
int n, q;
ll bit[maxn];
void add(int x, ll val){ //给bit[x]加上val
for(; x <= n; x += x&(-x))
bit[x] += val;
}
ll query(int x){ //查询bit[0]+...+bit[x]
ll ans = 0;
for(; x; x -= x&(-x))
ans += bit[x];
return ans;
}
int main()
{
cin>>n>>q;
ll val;
for(int i = 1; i<=n; i++){
cin>>val;
add(i, val);
}
int a,b,c;
while(q--){
cin>>a>>b>>c;
if(a==1){
add(b,c);
}
else{
cout<<query(c)-query(b-1)<<endl;
}
}
return 0;
}
区间修改、单点查询
操作1: 格式: 1 l r x 1\ l\ r\ x 1 l r x 含义:将 b i t [ l ] bit[l] bit[l] 到 b i t [ r ] bit[r] bit[r] 都加上 x x x
操作2: 格式:
2
x
2\ x
2 x 含义:输出
b
i
t
[
x
]
bit[x]
bit[x]
题目来源:LibreOj131
思路:利用差分的思想,在
b
i
t
[
l
]
bit[l]
bit[l] 加上
x
x
x,在
b
i
t
[
r
+
1
]
bit[r+1]
bit[r+1] 减去
x
x
x
#include<iostream>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 100;
int n, q;
ll bit[maxn];
void add(int x, ll val){
for(; x <= n; x += x&(-x))
bit[x] += val;
}
ll query(int x){
ll ans = 0;
for(; x; x -= x&(-x))
ans += bit[x];
return ans;
}
int main(){
cin>>n>>q;
ll val, x = 0;
for(int i =1 ;i <= n; i++){
cin>>val;
add(i, val-x);
x = val;
}
int a, b, c, d;
while(q--){
cin>>a;
if(a==1){
cin>>b>>c>>d;
add(b,d);
add(c+1,-d);
}
else{
cin>>d;
cout<<query(d)<<endl;
}
}
}
区间修改、区间求和
给一个区间同时加上
v
a
l
val
val
查询一个区间的和
题目来源:LibreOj132
∑
i
=
1
x
∑
j
=
1
i
b
[
j
]
=
∑
i
=
1
x
(
x
−
i
+
1
)
∗
b
[
j
]
=
(
x
+
1
)
∑
i
=
1
x
b
[
i
]
−
∑
i
=
1
x
i
∗
b
[
i
]
\sum_{i=1}^{x}\sum_{j=1}^ib[j]=\sum_{i=1}^x(x-i+1)*b[j]=(x+1)\sum_{i=1}^xb[i]-\sum_{i=1}^xi*b[i]
i=1∑xj=1∑ib[j]=i=1∑x(x−i+1)∗b[j]=(x+1)i=1∑xb[i]−i=1∑xi∗b[i]
#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 10;
int n, q;
ll bit[2][maxn]; //两个树状数组
//bit[0]维护di, bit[1] 维护di*i
void add(int x, ll val){
for(int i = x; i <= n; i += i&(-i)){
bit[0][i] += val;
bit[1][i] += val * x;
}
}
ll query(int x){
ll ans = 0;
for(int i = x; i; i -= i&(-i))
ans += (x + 1) * bit[0][i] - bit[1][i];
return ans;
}
ll ask(int l,int r){
return query(r) - query(l-1);
}
int main()
{
cin>>n>>q;
ll val, x = 0;
for(int i = 1; i <= n ;i++){
scanf("%lld",&val);
add(i, val - x);
x = val;
}
int op,l,r;
while(q--){
scanf("%d",&op);
if(op == 1){
scanf("%d%d%lld",&l,&r,&val);
add(l, val);
add(r+1, -val);
}
else{
scanf("%d%d",&l,&r);
cout<<ask(l,r)<<endl;
}
}
return 0;
}
二维树状数组:单点修改、区间查询
问题描述:给出一个
n
∗
m
n*m
n∗m 零矩阵
A
A
A,有如下操作:
1
x
y
k
1\ x\ y\ k
1 x y k :给
A
x
,
y
A_{x,y}
Ax,y 增加
k
k
k。
2
a
b
c
d
2\ a\ b\ c\ d
2 a b c d :询问
(
a
,
b
)
(a,b)
(a,b) 到
(
c
,
d
)
(c,d)
(c,d) 的和。
题目来源:LibreOj133
#include<iostream>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn = pow(2,12) + 10;
ll bit[maxn][maxn];
int n, m;
void add(int x, int y, ll val){
for(int i = x; i <= n; i += i&(-i))
for(int j = y; j <= m; j += j&(-j))
bit[i][j] += val;
}
ll query(int x, int y){
ll ans = 0;
for(int i = x; i; i -= i&(-i))
for(int j = y; j; j -= j&(-j))
ans += bit[i][j];
return ans;
}
ll ask(int a, int b, int c, int d){
return query(c, d) - query(c, b-1) - query(a-1,d) + query(a-1, b-1);
}
int main()
{
cin>>n>>m;
int op, a, b, c, d;
while(cin>>op){
if(op == 1){
cin>>a>>b>>c;
add(a, b, c);
}
else{
cin>>a>>b>>c>>d;
cout<<ask(a, b, c, d)<<endl;
}
}
return 0;
}
二维树状数组:区间修改、单点查询
对于一个初始为零矩阵,有两个操作:
1
a
b
c
d
k
1\ a\ b\ c\ d\ k
1 a b c d k:在区间
(
a
,
b
)
(a,b)
(a,b) 和
(
c
,
d
)
(c,d)
(c,d) 区间加上
k
k
k.
2
x
y
2\ x\ y
2 x y:查询点
(
x
,
y
)
(x,y)
(x,y) 的值.
#include<iostream>
#include<string>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn = 1000 + 10;
ll bit[maxn][maxn];
int n,q;
void add(int x, int y, ll val){
for(int i = x; i <= n; i += i&(-i))
for(int j = y; j <= n; j += j&(-j))
bit[i][j] += val;
}
ll query(int x, int y){
ll ans = 0;
for(int i = x; i ; i -= i&(-i))
for(int j = y; j; j -= j&(-j))
ans += bit[i][j];
return ans;
}
int main()
{
cin>>n>>q; //q次询问
int op,a,b,c,d,x,y;
ll val;
while(q--){
cin>>op;
if(op == 1){
cin>>a>>b>>c>>d>>val;
add(a, b, val);
add(a, d+1, -val);
add(c+1, b, -val);
add(c+1, d+1, val);
}
else{
cin>>x>>y;
cout<<query(x, y)<<endl;
}
}
return 0;
}
二维树状数组:区间修改、区间查询
题目来源:LibreOj135
∑
i
=
1
x
∑
j
=
1
y
∑
k
=
1
i
∑
h
=
1
j
d
[
h
]
[
k
]
\sum_{i=1}^x\sum_{j=1}^y\sum_{k=1}^i\sum_{h=1}^jd[h][k]
i=1∑xj=1∑yk=1∑ih=1∑jd[h][k]
=
∑
i
=
1
x
∑
j
=
1
y
d
[
i
]
[
j
]
∗
(
x
+
1
−
i
)
∗
(
y
+
1
−
j
)
=\sum_{i=1}^x\sum_{j=1}^yd[i][j]*(x+1-i)*(y+1-j)
=i=1∑xj=1∑yd[i][j]∗(x+1−i)∗(y+1−j)
=
(
x
+
1
)
∗
(
y
+
1
)
∗
∑
i
=
1
x
∑
j
=
1
y
d
[
i
]
[
j
]
=(x+1)*(y+1)*\sum_{i=1}^x\sum_{j=1}^yd[i][j]
=(x+1)∗(y+1)∗i=1∑xj=1∑yd[i][j]
−
(
y
+
1
)
∗
∑
i
=
1
x
∑
j
=
1
y
d
[
i
]
[
j
]
∗
i
-(y+1)*\sum_{i=1}^x\sum_{j=1}^yd[i][j]*i
−(y+1)∗i=1∑xj=1∑yd[i][j]∗i
−
(
x
+
1
)
∗
∑
i
=
1
x
∑
j
=
1
y
d
[
i
]
[
j
]
∗
j
-(x+1)*\sum_{i=1}^x\sum_{j=1}^yd[i][j]*j
−(x+1)∗i=1∑xj=1∑yd[i][j]∗j
+
∑
i
=
1
x
∑
j
=
1
y
d
[
i
]
[
j
]
∗
i
∗
j
+\sum_{i=1}^x\sum_{j=1}^yd[i][j]*i*j
+i=1∑xj=1∑yd[i][j]∗i∗j
#include<iostream>
#include<string>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn = 2048+ 10;
ll bit[4][maxn][maxn];
int n,m,q;
void add(int x, int y, ll val){
for(int i = x; i <= n; i += i&(-i))
for(int j = y; j <= m; j += j&(-j)){
bit[0][i][j] += val;
bit[1][i][j] += val*x;
bit[2][i][j] += val*y;
bit[3][i][j] += val*x*y;
}
}
ll query(int x, int y){
ll ans = 0;
for(int i = x; i ; i -= i&(-i))
for(int j = y; j; j -= j&(-j))
ans += (x+1)*(y+1)*bit[0][i][j]
- (y+1)*bit[1][i][j]
- (x+1)*bit[2][i][j]
+ bit[3][i][j];
return ans;
}
ll ask(int a,int b,int c,int d){
return query(c,d) - query(c,b-1) - query(a-1,d) + query(a-1,b-1);
}
int main()
{
cin>>n>>m; //q次询问
int op,a,b,c,d;
ll val;
while(cin>>op){
if(op == 1){
cin>>a>>b>>c>>d>>val;
add(a, b, val);
add(a, d+1, -val);
add(c+1, b, -val);
add(c+1, d+1, val);
}
else{
cin>>a>>b>>c>>d;
cout<<ask(a, b,c,d)<<endl;
}
}
return 0;
}
实战一:逆序对 | 冒泡排序的交换次数
问题描述:求一个序列的逆序对。
题目来源:poj2299
//可以这样写,把a[i]当成下标,如果这样当a[i]的数据范围很大,就不太好了
for(int i = n; i>=1; i--){
add(a[i], 1);
ans += query(a[i] - 1);
}
可以给每个数弄一个编号,然后排个序,转化为求编号的逆序对,减小树状数组的开销
1 2 3 4 //编号
5 1 3 6 //数值 逆序对为2
------排序后------------
2 3 1 4 //编号 逆序对为2
1 3 5 6 //数值
sort(a+1, a+n+1, cmp); //按照a的值从小到大排序
for(int i = n; i>=1; i--){
add(a[i].pos, 1);
ans += query(a[i].pos - 1);
}
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
typedef long long ll;
const int maxn = 5e5 + 10;
struct node{
int val, pos;
}a[maxn];
ll bit[maxn];
int n;
void add(int x, ll val){
for(int i = x; i <= n; i += i&(-i))
bit[i] += val;
}
ll query(int x){
ll ans = 0;
for(int i = x; i; i -= i&(-i))
ans += bit[i];
return ans;
}
void init(){
memset(a, 0, sizeof a);
memset(bit, 0, sizeof bit);
}
bool cmp(node a, node b){
return a.val < b.val;
}
int main()
{
while(~scanf("%d",&n), n){
init();
for(int i = 1; i <= n; i++){
//cin>>a[i].val; cin真的比scanf慢多了
scanf("%d",&a[i].val);
a[i].pos = i;
}
sort(a+1,a+n+1,cmp);
ll ans = 0;
for(int i = n; i>=1; i--){
add(a[i].pos, 1);
ans += query(a[i].pos - 1);
}
cout<<ans<<endl;
}
return 0;
}
实战二:给气球涂色
问题描述:有
n
n
n 个气球,编号依次为
1
,
2
,
.
.
.
,
n
1,2,...,n
1,2,...,n,给出
l
l
l,
r
r
r ,对这编号之间的气球都涂上一层颜色,进行
n
n
n 次操作。最后打印出每个气球被涂过多少层颜色。
题目来源:hdu1556
#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 10;
ll bit[maxn];
int n;
void add(int x,ll val){
for(; x <= n; x += x&(-x))
bit[x] += val;
}
ll query(int x){
ll ans = 0;
for(; x; x -= x&(-x)){
ans += bit[x];
}
return ans;
}
int main(){
while(cin>>n,n){
memset(bit,0,sizeof bit);
ll a,b;
for(int i = 0;i<n;i++){
cin>>a>>b;
add(a,1);
add(b+1,-1);
}
for(int i=1;i<n;i++){
cout<<query(i)<<" ";
}
cout<<query(n)<<endl;
}
return 0;
}
实战三:01矩阵
问题描述:有一个初始为零的
n
∗
n
n*n
n∗n 矩阵,有两个操作:
C
a
b
c
d
C\ a\ b\ c\ d
C a b c d:对
(
a
,
b
)
(a,b)
(a,b) 和
(
c
,
d
)
(c,d)
(c,d) 的
0
0
0 变
1
1
1,
1
1
1 变
0
0
0
Q
x
y
Q\ x\ y
Q x y:查询点
(
x
,
y
)
(x,y)
(x,y) 的数值
题目来源:poj2155
#include<iostream>
#include<string>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn = 1000 + 10;
ll bit[maxn][maxn];
int n,q;
void add(int x, int y, ll val){
for(int i = x; i <= n; i += i&(-i))
for(int j = y; j <= n; j += j&(-j))
bit[i][j] += val;
}
ll query(int x, int y){
ll ans = 0;
for(int i = x; i ; i -= i&(-i))
for(int j = y; j; j -= j&(-j))
ans += bit[i][j];
return ans;
}
int main()
{
int t, a, b, c, d;
string op;
cin>>t;
while(t--){
memset(bit, 0,sizeof bit);
cin>>n>>q;
while(q--){
cin>>op;
if(op == "C"){
scanf("%d%d%d%d",&a,&b,&c,&d);
add(a,b,1);
add(a,d+1,1);
add(c+1,b,1);
add(c+1,d+1,1);
}
else if(op == "Q"){
cin>>a>>b;
cout<<query(a,b)%2<<endl;;
}
}
if(t)
cout<<endl;
}
return 0;
}