树状数组2

树状数组1

1 最长上升子序列

洛谷B3637 最长上升子序列

1.1 题目描述

给出一个由 n ( n ≤ 5000 ) n(n\le 5000) n(n5000) 个不超过 1 0 6 10^6 106 的正整数组成的序列。请输出这个序列的最长上升子序列的长度。

最长上升子序列是指,从原序列中按顺序取出一些数字排在一起,这些数字是逐渐增大的。

1.1.2 输入格式

第一行,一个整数 n n n,表示序列长度。

第二行有 n n n 个整数,表示这个序列。

1.1.3 输出格式

一个整数表示答案。

1.1.4 样例

样例输入

6
1 2 4 1 3 4

样例输出

4

1.2 题解

f i f_i fi:以编号 i i i 的元素结尾的最长上升子序列。

f i = m a x ( f j + 1 ) ( j < i 且 a j < a i ) f_i = max(f_j + 1) (j < i 且 a_j < a_i) fi=max(fj+1)(j<iaj<ai)

考虑使用 树状数组 来保存 f i f_i fi 的值。

遍历,查找出 f 1 f_1 f1 f i − 1 f_{i-1} fi1 之间的最大值,即为所求的 f j f_j fj.

最后找到 f 1 f_1 f1 f n f_n fn 之间的最大值,即为答案。

#include <bits/stdc++.h>
typedef long long ll;
const ll N = 1e6 + 10;
ll max_a, n, c[N], a[N];
inline ll lowbit(ll n) {return n & (-n);}
inline void updata(ll x, ll y) {
    for (ll i = x;i <= max_a; i += lowbit(i)) 
		c[i] = std::max(c[i], y);
}
inline ll find(ll x) {
    ll res = 0;
    for (ll i = x;i >= 1; i -= lowbit(i)) 
		res = std::max(res, c[i]);
    return res;
}
int main() {
	scanf("%lld", &n);
	for (ll i = 1; i <= n; i++) {
		scanf("%lld", &a[i]);
		a[i]++;
		max_a = std::max(a[i], max_a);
	}
	for (ll i = 1; i <= n; i++) {
		ll k = find(a[i] - 1) + 1;
		updata(a[i], k);
	} 
	printf("%lld", find(max_a));
	return 0;
}

2 区间改值和单值查询

2.1 题目描述

给定数组 a 1 , a 2 , . . . , a n ( ∣ a i ∣ ≤ 1 0 6 ) a_1,a_2,...,a_n(|a_i|\le 10^6) a1,a2,...,an(ai106),进行 q q q 次操作,操作有两种:

1 l r k:将 a l a_l al ~ a r ( 1 ≤ l ≤ r ≤ n ) a_r(1\le l\le r\le n) ar(1lrn) 全部加上 k ( ∣ k ∣ ≤ 1 0 6 ) k (|k|\le 10^6) k(k106)

2 k:输出 a k a_k ak

2.1.2 输入格式

第一行:输入两个数 n n n q ( 1 ≤ n , q ≤ 1 0 6 ) q(1\le n,q\le 10^6) q(1n,q106),表示给定数组的长度和操作数。

第二行:输入 n n n 个数,表示长度为 n n n 的给定数组。

接下来 q q q 行:每行输入表示如题的某一种操作。

2.1.3 输出格式

对于每个2操作,输出对应结果。

2.1.4 样例

样例输入

3 2
1 2 3
1 1 3 1
2 2

样例输出

3

2.2 题解

运用树状数组实现差分

改值:将 [ l , r ] [l,r] [l,r] 区间的数都加 k k k ,等同于 [ l , n ] [l,n] [l,n] 区间加 k k k [ r + 1 , n ] [r+1,n] [r+1,n] 区间减 k k k.

查询:差分数组的前缀和结果等于原数组,所以 a k = ∑ i = 1 k c k a_k=\sum_{i=1}^kc_k ak=i=1kck.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 1e6 + 10;
ll mina, n, q, c[N], a[N];
ll lowbit(ll n) {return n & (-n);}
void updata(ll l, ll r, ll x) {
    for (ll i = l;i <= n; i += lowbit(i)) 
		c[i] += x;
	for (ll i = r + 1;i <= n; i += lowbit(i)) 
		c[i] -= x;
}
ll count(ll x) {
    ll res = 0;
    for (ll i = x;i >= 1; i -= lowbit(i)) 
		res += c[i];
    return res;
}
int main() {
	scanf("%lld%lld", &n, &q);
	for (ll i = 1; i <= n; i++) {
		cin >> a[i];
		updata(i, i, a[i]);
	}
	while (q--) {
		ll op, k, x, l, r; 
		scanf("%lld", &op);
		if (op == 1) {
			scanf("%lld%lld%lld", &l, &r, &x);
			updata(l, r, x);
		} else {
			scanf("%lld", &k);
			printf("%lld\n", count(k));
		}
	}
	return 0;
}

3 区间改值和区间求和

3.1 题目描述

给定数组 a 1 , a 2 , . . . , a n ( ∣ a i ∣ ≤ 1 0 6 ) a_1,a_2,...,a_n(|a_i|\le 10^6) a1,a2,...,an(ai106),进行 q q q 次操作,操作有两种:

1 l r k:将 a l a_l al ~ a r ( 1 ≤ l ≤ r ≤ n ) a_r(1\le l\le r\le n) ar(1lrn) 全部加上 k ( ∣ k ∣ ≤ 1 0 6 ) k (|k|\le 10^6) k(k106)

2 l r:输出 ∑ i = l r a i \sum_{i=l}^ra_i i=lrai

3.1.2 输入格式

第一行:输入两个数 n n n q ( 1 ≤ n , q ≤ 1 0 6 ) q(1\le n,q\le 10^6) q(1n,q106),表示给定数组的长度和操作数。

第二行:输入 n n n 个数,表示长度为 n n n 的给定数组。

接下来 q q q 行:每行输入表示如题的某一种操作。

3.1.3 输出格式

对于每个2操作,输出对应结果。

3.1.4 样例

样例输入

5 3
2 6 6 1 1
2 1 4
1 2 5 10
2 1 3

样例输出

15
34

3.2 题解

∑ i = 1 n a i = ∑ i = 1 n ∑ j = 1 i c i = ∑ i = 1 n ( n − i + 1 ) c i = ∑ i = 1 n ( n + 1 ) c i − ∑ i = 1 n i ⋅ x i \sum_{i=1}^{n}a_i=\sum_{i=1}^{n}\sum_{j=1}^ic_i =\sum_{i=1}^n(n-i+1)c_i =\sum_{i=1}^n(n+1)c_i-\sum_{i=1}^ni\cdot x_i i=1nai=i=1nj=1ici=i=1n(ni+1)ci=i=1n(n+1)cii=1nixi

两个树状数组,一个差分(也就是计算 ∑ i = 1 n ( n + 1 ) c i \sum_{i=1}^n(n+1)c_i i=1n(n+1)ci 这一部分),另一个计算 ∑ i = 1 n i ⋅ x i \sum_{i=1}^ni\cdot x_i i=1nixi 的值。
∑ i = l r a i = ∑ i = 1 r a i − ∑ i = 1 l − 1 a i \sum_{i=l}^ra_i=\sum_{i=1}^ra_i-\sum_{i=1}^{l-1}a_i i=lrai=i=1raii=1l1ai
根据这个公式,可求出操作2的值。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 1e6 + 10;
ll mina, n, q, c1[N], c2[N], a[N];
ll lowbit(ll n) {return n & (-n);}
void updata(ll l, ll r, ll x) {
    for (ll i = l;i <= n; i += lowbit(i)) 
		c1[i] += x, c2[i] += x * l;
	for (ll i = r + 1;i <= n; i += lowbit(i)) 
		c1[i] -= x, c2[i] -= x * (r + 1);
}
ll count(ll l, ll r) {
    ll res = 0;
    for (ll i = r;i >= 1; i -= lowbit(i)) 
		res += c1[i] * (r + 1) - c2[i];
	for (ll i = l - 1;i >= 1; i -= lowbit(i)) 
		res -= c1[i] * l - c2[i];
    return res;
}
int main() {
	scanf("%lld%lld", &n, &q);
	for (ll i = 1; i <= n; i++) {
		cin >> a[i];
		updata(i, i, a[i]);
	}
	while (q--) {
		ll op, k, x, l, r; 
		scanf("%lld", &op);
		if (op == 1) {
			scanf("%lld%lld%lld", &l, &r, &x);
			updata(l, r, x);
		} else {
			scanf("%lld%lld", &l, &r);
			printf("%lld\n", count(l, r));
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值