树状数组

单点修改、区间求和

操作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=1xj=1ib[j]=i=1x(xi+1)b[j]=(x+1)i=1xb[i]i=1xib[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 nm 零矩阵 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=1xj=1yk=1ih=1jd[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=1xj=1yd[i][j](x+1i)(y+1j) = ( 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=1xj=1yd[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=1xj=1yd[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=1xj=1yd[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=1xj=1yd[i][j]ij

#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 nn 矩阵,有两个操作:
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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值