书本配套OJ
我校OJ
树状数组知识点:
与线段树相比功能比较单一,不够灵活,但胜在简短易写,可以用来打辅助。
注意事项
题目描述
给定n个数列,规定有两种操作,一是修改某个元素,二是求子数列[a,b]的连续和。数列元素个数最多10万个,询问操作最多10万次。
解题思路
模板题。
代码示例
#include
using namespace std;
const int N = 1e5+10;
typedef long long ll;
int n,m;
ll A[N],c[N];
void Add(int x,int y){
/*把第x位置上的数+y*/
for(;x <= n;x += x&-x) c[x] += y;
}
ll ask(int x){
/*返回[1,x]元素和*/
ll res = 0;
for(;x;x -= x&-x) res += c[x];
return res;
}
int main(){
scanf("%d%d",&n,&m);
int k,a,b;
for(int i = 1;i <= n;i++) scanf("%lld",A+i);
for(int i = 1;i <= n;i++) Add(i,A[i]);
for(int i = 1;i <= m;i++){
scanf("%d%d%d",&k,&a,&b);
if(k) Add(a,b);
else printf("%lld\n",ask(b)-ask(a-1));
}
return 0;
}
题意简述
给定 n 个点,定义每个点的等级是在该点左下方(含正左、正下)的点的数目,试统计每个等级有多少个点。
(详细描述见原题面)
解题思路
由于y是升序的,我们只需要记录比当前x小的star有多少个即可得知其等级 x ,然后用num[x]表示等级为x的星星个数,最终答案就是num数组。
CDQ分治也可以写,都是先将一维给变的有序,再统计另一维度。
代码示例
#include
using namespace std;
const int N = 4e4+10;
int c[N],n;
int ans[N];
void Add(int x,int y){ //若x=0则死循环
for(;x < N;x += x&-x) c[x] += y;
}
int ask(int x){
int res = 0;
for(;x;x -= x&-x) res += c[x];
return res;
}
int main(){
// freopen("123.txt","r",stdin);
scanf("%d",&n);
for(int i = 1,x,y;i <= n;i++){
scanf("%d%d",&x,&y);
ans[ask(x+1)]++;//要注意x = 0这种情况
Add(x+1,1);
}
for(int i = 0;i < n;i++) printf("%d\n",ans[i]);
return 0;
}
题意简述
原题来自:Vijos P1448
校门外有很多树,学校决定在某个时刻在某一段种上一种树,保证任一时刻不会出现两段相同种类的树,现有两种操作:
解题思路
刚开始以为是区间修改的题,后来发现不是。这题利用了差分数组的思想。如果我们假设c0[ x ] 存放 [1, x] 的左端点数量,c1[]存放右端点数量;如果所求区间为[l , r],那么一共只可能有 6 种不同的线段:
那么我们用 l 右侧所有的右端点的数量,减去 r 右侧左端点的数量,可以画图理解,简单。
代码示例
#include
using namespace std;
const int N = 1e5+10;
typedef long long ll;
int n,m;
ll A[N],c[2][N];
void Add(int ty,int x,int y){
/*把第x位置上的数+y*/
for(;x < N;x += x&-x) c[ty][x] += y;
}
ll ask(int ty,int x){
/*返回[1,x]元素和*/
ll res = 0;
for(;x;x -= x&-x) res += c[ty][x];
return res;
}
int main(){
scanf("%d%d",&n,&m);
int k,a,b;
for(int i = 1;i <= m;i++){
scanf("%d%d%d",&k,&a,&b);
if(k == 1){
Add(0,a,1); Add(1,b,1);
}else{
/*分类讨论,所有线段共6种情况 */
int t1 = ask(1,N-1) - ask(1,a-1);
int t2 = ask(0,N-1) - ask(0,b);
printf("%d\n",t1-t2);
}
}
return 0;
}
题意简述
NK 中学组织同学们去五云山寨参加社会实践活动,按惯例要乘坐火车去。由于 NK 中学的学生很多,在火车开之前必须清点好人数。
初始时,火车上没有学生。当同学们开始上火车时,年级主任从第一节车厢出发走到最后一节车厢,每节车厢随时都有可能有同学上下。年级主任走到第 m 节车厢时,他想知道前 m 节车厢上一共有多少学生,但是他没有调头往回走的习惯。也就是说每次当他提问时,m 总会比前一次大。
解题思路
也是模板题。
代码示例
#include
using namespace std;
const int N = 5e5+10;
int c[N] ,n,k;
void Add(int x,int y){
for(;x < N;x += x&-x) c[x] += y;
}
int ask(int x){
int res = 0;
for(;x;x -= x&-x) res += c[x];
return res;
}
int main(){
//freopen("123.txt","r",stdin);
char op[5];
scanf("%d%d",&n,&k);
for(int i = 1,x,y;i <= k;i++){
scanf("%s",op);
if(op[0] == 'A'){
scanf("%d",&x); printf("%d\n",ask(x));
}else if(op[0] == 'B'){
scanf("%d%d",&x,&y); Add(x,y);
}else{
scanf("%d%d",&x,&y); Add(x,-y);
}
}
return 0;
}
题意简述
题目来源:CQOI 2006
有一个 n 个元素的数组,每个元素初始均为 0。有 m 条指令,要么让其中一段连续序列数字反转——0 变 1,1 变 0(操作 1),要么询问某个元素的值(操作 2)。
解题思路
一个位置如果反转偶数次,那么它还是0,否则是1,因此我们只需要统计每个位置被反转了几次即可,用到了树状数组区间修改单点查询。树状数组区间修改的推导就不写了,书上有。
代码示例
#include
using namespace std;
const int N = 1e5+10;
int c[2][N],n,m ,num[N];
int getInt() {
int ans = 0;
bool neg = false;
char c = getchar();
while (c!='-' && (c<'0' || c>'9')) c = getchar();
if (c == '-') neg = true, c = getchar();
while (c>='0' && c<='9')
ans = ans*10 + c-'0', c = getchar();
return neg ? -ans : ans;
}
void Add(int ty,int x,int y){
for(;x <= n;x += x&-x) c[ty][x] += y;
}
int ask(int ty,int x){
int res = 0;
for(;x;x -= x&-x) res += c[ty][x];
return res;
}
int main(){
//freopen("123.txt","r",stdin);
n = getInt(); m = getInt();
for(int i = 1,x,y,z;i <= m;i++){
z = getInt();
if(z == 1){
x = getInt(); y = getInt();
Add(0,x,1); Add(0,y+1,-1);
Add(1,x,x); Add(1,y+1,-y-1);
}else {
y = getInt(); x = y;
int res = (y+1)*ask(0,y) - ask(1,y) - x*ask(0,x-1) + ask(1,x-1);
printf("%d\n",res&1);
}
}
return 0;
}
题意简述
这是一道模板题。
给出一个 n×m 的零矩阵 A,你需要完成如下操作:
1 x y k:表示元素 Ax,y自增 k;
2 a b c d:表示询问左上角为 (a,b),右下角为 (c,d) 的子矩阵内所有数的和。
解题思路
是二维树状数组模板题,但是空间卡的很紧,开到N = 5000就险过,开到N = (1<<12)+10就好了一点。
和vijos那个原题不同,数据不一样,范围也不一样,这题不会出现下标为0的情况。就注意一下二维更新和查询时,每次的y需要恢复原值即可,因此需要用个变量存放一下y初值。
代码示例
#include
using namespace std;
const int N = (1<<12)+10;
typedef long long ll;
ll c[N][N];
int n ,m;
int getInt() {
int ans = 0;
bool neg = false;
char c = getchar();
while (c!='-' && (c<'0' || c>'9')) c = getchar();
if (c == '-') neg = true, c = getchar();
while (c>='0' && c<='9')
ans = ans*10 + c-'0', c = getchar();
return neg ? -ans : ans;
}
void Add(int x,int y,int z){
//printf("%d %d %d\n",x,y,z);
for(;x <= n;x += x&-x)
for(int ty = y;ty <= m;ty += ty&-ty) c[x][ty] += z;
}
ll ask(int x,int y){
ll res = 0;
for(;x;x -= x&-x)
for(int ty = y;ty;ty -= ty&-ty) res += c[x][ty];
return res;
}
int main(){
int a,b,c,d,x,y,k,op;
//freopen("123.txt","r",stdin);
n = getInt(); m = getInt();
while(scanf("%d",&op) != EOF){
if(op == 1){
x = getInt(); y = getInt(); k = getInt();
Add(x,y,k);
}else{
a = getInt(); b = getInt(); c = getInt();d = getInt();
printf("%lld\n",ask(c,d)-ask(c,b-1)-ask(a-1,d)+ask(a-1,b-1));
}
}
return 0;
}