【模板】树状数组
同线段树,迄今为止,NOIP上还没有以树状数组为正解的题目,所以树状数组通常用来在面对区间相关问题想不到正解时打一个高分暴力。( IOI 及 ACM 选手请忽略)
洛谷 P3374 【模板】树状数组 1(单点更新与区间求和)
题目描述
如题,已知一个数列,你需要进行下面两种操作:
1.将某一个数加上x
2.求出某区间每一个数的和
输入输出格式
输入格式:
第一行包含两个整数N、M,分别表示该数列数字的个数和操作的总个数。
第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值。
接下来M行每行包含3或4个整数,表示一个操作,具体如下:
操作1: 格式:1 x k 含义:将第x个数加上k
操作2: 格式:2 x y 含义:输出区间[x,y]内每个数的和
输出格式:
输出包含若干行整数,即为所有操作2的结果。
输入输出样例
输入样例#1:
5 5
1 5 4 2 3
1 1 3
2 2 5
1 3 -1
1 4 2
2 1 4
输出样例#1:
14
16
说明
时空限制:1000ms,128M
数据规模:
对于30%的数据:N<=8,M<=10
对于70%的数据:N<=10000,M<=10000
对于100%的数据:N<=500000,M<=500000
代码如下
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
using namespace std;
const int max_n = 500007, max_m = 500007;
int c[max_n]; //存储前缀和(树状)
int n, m;
template<class T>void read(T &x) //输入优化
{
int f = 0; x = 0; char ch = getchar();
while(ch < '0' || ch > '9') f |= (ch == '-'), ch = getchar();
while(ch >= '0' && ch <= '9') x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
x = f? -x : x;
}
void write(long long x) //输出优化
{
if(x < 0) putchar('-'), x = -x;
if(x > 9) write(x / 10);
putchar(x % 10 + '0');
}
int lowbit(int x){return x & (-x);} //追溯其父节点或下辖第一个没有关系的点:(所有操作都是围绕这个)
void update(int x, int num) //修改元素的值
{
while(x <= n)
{
c[x] += num;
x += lowbit(x);
}
}
int getsum(int x) //求数组前n项和
{
int s = 0;
while(x)
{
s += c[x];
x -= lowbit(x);
}
return s;
}
int sum(int x, int y){return getsum(y) - getsum(x - 1);} //区间求和
int main()
{
read(n);read(m);
int x, y, k;
for(int i = 1; i <= n; i++) read(k), update(i, k);
for(int i = 1; i <= m; i++)
{
read(k);read(x);read(y);
if(k == 1) update(x, y);
else write(sum(x, y)), putchar(10);
}
return 0;
}
洛谷P3368 【模板】树状数组 2(区间更新,单点求值)
题目描述
如题,已知一个数列,你需要进行下面两种操作:
1.将某区间每一个数数加上x
2.求出某一个数的和
输入输出格式
输入格式:
第一行包含两个整数N、M,分别表示该数列数字的个数和操作的总个数。
第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值。
接下来M行每行包含2或4个整数,表示一个操作,具体如下:
操作1: 格式:1 x y k 含义:将区间[x,y]内每个数加上k
操作2: 格式:2 x 含义:输出第x个数的值
输出格式:
输出包含若干行整数,即为所有操作2的结果。
输入输出样例
输入样例#1:
5 5
1 5 4 2 3
1 2 4 2
2 3
1 1 5 -1
1 3 5 7
2 4
输出样例#1:
6
10
说明
时空限制:1000ms,128M
数据规模:
对于30%的数据:N<=8,M<=10
对于70%的数据:N<=10000,M<=10000
对于100%的数据:N<=500000,M<=500000
//说明同上,建树方式与上面不同
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
const int max_n = 500007, max_m = 500007;
int tree[max_n]; //注意这里存储的不再是前缀和,而是每一个元素的变化量
int a[max_n];
int n, m;
template<class T>void read(T &x)
{
int f = 0; x = 0; char ch = getchar();
while(ch < '0' || ch > '9') f |= (ch == '-'), ch = getchar();
while(ch >= '0' && ch <= '9') x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
x = f? -x : x;
}
void write(long long x)
{
if(x < 0) putchar('-'), x = -x;
if(x > 9) write(x / 10);
putchar(x % 10 + '0');
}
void update(int x, int num){for(int i = x; i <= n; i += (i & (-i))) tree[i] += num;}
int getsum(int x){int sum = 0; for(int i = x; i; i -= (i & (-i))) sum += tree[i]; return sum;}
int main()
{
read(n);read(m);
for(int i = 1; i <= n; i++) read(a[i]);
for(int i = 1; i <= m; i++)
{
int k, x, y, l;
read(l);
if(l == 1)
{
read(x);read(y);read(k);
update(x, k);
update(y + 1, -k);
}
else
{
read(x);
write(a[x] + getsum(x));
putchar(10);
}
}
return 0;
}
洛谷P1439 【模板】最长公共子序列(单点修改,区间维护最值)(最长上升子序列)
题目描述
给出1-n的两个排列P1和P2,求它们的最长公共子序列。
输入输出格式
输入格式:
第一行是一个数n,
接下来两行,每行为n个数,为自然数1-n的一个排列。
输出格式:
一个数,即最长公共子序列的长度
输入输出样例
输入样例#1:
5
3 2 1 4 5
1 2 3 4 5
输出样例#1:
3
说明
【数据规模】
对于50%的数据,n≤1000
对于100%的数据,n≤100000
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
#define LL long long
#define op operator
#define rt register
const int maxn=100010;
const int mod=1e9+7;
const LL INF=(LL)1<<60;
using namespace std;
LL _re,_ch; char _c;
template <class T> T Max(T a,T b){return a>b ? a : b;}
template <class T> T Min(T a,T b){return a<b ? a : b;}
template <class T> void in(T &x)
{
_re=0,_ch=1; _c=getchar();
while(_c<'0'||_c>'9') _ch= _c=='-' ? -1 : 1, _c=getchar();
while(_c>='0'&&_c<='9') _re=_re*10+_c-'0', _c=getchar();
x=_re*_ch;
}
template <class T> void put(T x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) put(x/10);
putchar(x%10+48);
}
int C[maxn], a[maxn], b[maxn], c[maxn], n, pos, ans;
inline void Insert(int pos, int vi) //单点修改
{
C[pos] = vi;
for(int x = pos ; x <= n ; x += x & -x)
C[x] = Max(C[x], vi);
}
inline int Query(int pos) //区间求最值
{
int re=0;
for(int x = pos ; x ; x -= x & -x)
re = Max(re, C[x]);
return re;
}
int main()
{
in(n);
for(rt int i = 1; i <= n; i++) in(a[i]), b[a[i]] = i;
for(rt int i = 1; i <= n; i++) in(c[i]), c[i] = b[c[i]];
for(rt int i = 1; i <= n; i++)
{
pos = Query(c[i] - 1 );
ans = Max(ans, pos + 1);
Insert(c[i], pos + 1);
}
put(ans);
return 0;
}
!注意:本篇博文只是简略介绍了线段树与贴上了个人习惯的模板代码,若想要详细了解学习线段树请搜索相关知识,对您造成的不便敬请谅解~
相关内容推荐:①百度百科——树状数组、②树状数组详解、③树状数组总结
博文修改记录:
1.2017.11.9 增添树状数组单点修改,求区间最值(树状数组求最长上升子序列)