题目描述:
C国的死对头A国这段时间正在进行军事演习,所以C国间谍头子Derek和他手下Tidy又开始忙乎了。A国在海岸线沿直线布置了N个工兵营地,Derek和Tidy的任务就是要监视这些工兵营地的活动情况。由于采取了某种先进的监测手段,所以每个工兵营地的人数C国都掌握的一清二楚,每个工兵营地的人数都有可能发生变动,可能增加或减少若干人手,但这些都逃不过C国的监视。 中央情报局要研究敌人究竟演习什么战术,所以Tidy要随时向Derek汇报某一段连续的工兵营地一共有多少人,例如Derek问:“Tidy,马上汇报第3个营地到第10个营地共有多少人!”Tidy就要马上开始计算这一段的总人数并汇报。但敌兵营地的人数经常变动,而Derek每次询问的段都不一样,所以Tidy不得不每次都一个一个营地的去数,很快就精疲力尽了,Derek对Tidy的计算速度越来越不满:“你个死肥仔,算得这么慢,我炒你鱿鱼!”Tidy想:“你自己来算算看,这可真是一项累人的工作!我恨不得你炒我鱿鱼呢!”无奈之下,Tidy只好打电话向计算机专家Windbreaker求救,Windbreaker说:“死肥仔,叫你平时做多点acm题和看多点算法书,现在尝到苦果了吧!”Tidy说:“我知错了。。。”但Windbreaker已经挂掉电话了。Tidy很苦恼,这么算他真的会崩溃的,聪明的读者,你能写个程序帮他完成这项工作吗?不过如果你的程序效率不够高的话,Tidy还是会受到Derek的责骂的. ## 输入 第一行一个整数T,表示有T组数据。 每组数据第一行一个正整数N(N<=50000),表示敌人有N个工兵营地,接下来有N个正整数,第i个正整数ai代表第i个工兵营地里开始时有ai个人(1<=ai<=50)。 接下来每行有一条命令,命令有4种形式: (1) Add i j,i和j为正整数,表示第i个营地增加j个人(j不超过30) (2)Sub i j ,i和j为正整数,表示第i个营地减少j个人(j不超过30); (3)Query i j ,i和j为正整数,i<=j,表示询问第i到第j个营地的总人数; (4)End 表示结束,这条命令在每组数据最后出现; 每组数据最多有40000条命令
输出
对第i组数据,首先输出“Case i:”和回车, 对于每个Query询问,输出一个整数并回车,表示询问的段中的总人数,这个数保持在int以内。
思路:
最近刚学的三个最新的数据结构都可以写这题,可以说这题很经典吧!不管对于那种数据结构都很典型,也能体现每种数据结构的特点,下面一一解释:
分块:
由于每次询问时都要查询一个区间的和,若每次都扫描一遍,肯定会超时,那么换一种想法,若将整个区间划分为若干个小块,存下他们的和,当访问一个区间时,其中包含的所有完整的小块便可直接O(1)查询和,至于两边多余的部分便可以暴力求和了,至于更新,则只需额外再修改一下该点所在的块的和即可。
树状数组:
树状数组我觉得是三个数据结构中最难理解的,咳咳。。比如有一个长度为6的数组,二进制表示为110,此时可以将其拆分为110=100+10,进行处理,至于如何拆分,用lowbit即可,这样只需预处理出所有这样形式的和即可,这样做大大减少了复杂度,至于如何实现,可见下方代码。
线段树:
这种方法就比较套路了,将(1-n)的数组,划分为一棵二叉树,分割为一个个小区间,分别存在每个子树(或叶子)中,最后查询时用树形遍历即可,这样的复杂度也会大大减少。线段树的大致结构为,build()函数建树,update()函数更新结构,最后query()函数查询信息,三个函数的写法都十分相似,但要学会区分。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179//分块做法
const int maxn=50005,S=100;
int a[maxn],sum[maxn];
int query(int l,int r){
int pl=l/S,pr=r/S,ans=0;
if(pl==pr){
for(int i=l;i<=r;i++)
ans+=a[i];
}
else {
for(int i=l;i<(pl+1)*S;i++)ans+=a[i];
for(int i=pr*S;i<=r;i++)ans+=a[i];
for(int i=pl+1;i<pr;i++)ans+=sum[i];
}
return ans;
}
int main(){
int T,kase=0;
scanf("%d",&T);
while(T--){
int n,tot=0;
scanf("%d",&n);
for(int i=0;i<n;i++)a[i]=sum[i]=0;
for(int i=0;i<n;i++){
scanf("%d",&a[i]);
tot+=a[i];
if(i%S==S-1)sum[i/S]=tot,tot=0;
}
printf("Case %d:\n",++kase);
char cmd[10];
while(scanf("%s",cmd)==1){
if(cmd[0]=='E')break;
int x,y;
scanf("%d%d",&x,&y);
x--,y--;
if(cmd[0]=='Q'){
printf("%d\n",query(x,y));
}
else if(cmd[0]=='A'){
y++;
int px=x/S;
a[x]+=y;
sum[px]+=y;
}
else if(cmd[0]=='S'){
y++;
int px=x/S;
a[x]-=y;
sum[px]-=y;
}
}
}
return 0;
}
//树状数组写法
const int maxn=50005;
int a[maxn],c[maxn],n;
int lowbit(int x){
return x&(-x);
}
//以下两个函数很重要,要完全理解。
int sum(int x){
int ans=0;
while(x>0){
ans+=c[x];
x-=lowbit(x);
}
return ans;
}
void update(int x,int y){
while(x<=n){
c[x]+=y;
x+=lowbit(x);
}
}
int main(){
int T,kase=0;
scanf("%d",&T);
while(T--){
scanf("%d",&n);
memset(c,0,sizeof(c));
for(int i=1;i<=n;i++)scanf("%d",&a[i]),update(i,a[i]);
char cmd[10];
printf("Case %d:\n",++kase);
while(scanf("%s",cmd)==1){
if(cmd[0]=='E')break;
int x,y;
scanf("%d%d",&x,&y);
if(cmd[0]=='A'){
update(x,y);
a[x]+=y;
}
else if(cmd[0]=='S'){
update(x,-y);
a[x]+=y;
}
else if(cmd[0]=='Q')
printf("%d\n",sum(y)-sum(x-1));
}
}
return 0;
}
//线段树写法
int a[M];
struct tree{
int l,r,sum;
}tree[M*4];
void Up(int p){
tree[p].sum=tree[p<<1].sum+tree[p<<1|1].sum;
}
//以下三个函数十分重要,要完全理解
void build(int l,int r,int p){
tree[p].l=l,tree[p].r=r;
if(l==r){
tree[p].sum=a[l];
return;
}
int mid=(l+r)>>1;
build(l,mid,2*p);
build(mid+1,r,2*p+1);
Up(p);
}
void add(int x,int a,int p){
if(tree[p].l==tree[p].r){
tree[p].sum+=a;
return;
}
int mid=(tree[p].l+tree[p].r)>>1;
if(x<=mid)add(x,a,p<<1);
else add(x,a,p<<1|1);
Up(p);
}
int query(int l,int r,int p){
//printf("%d %d %d\n",l,r,p);
if(tree[p].l==l&&tree[p].r==r){
return tree[p].sum;
}
int mid=(tree[p].l+tree[p].r)>>1;
if(r<=mid)return query(l,r,p<<1);
else if(l>mid)return query(l,r,p<<1|1);
else {
return query(l,mid,p<<1)+query(mid+1,r,p<<1|1);
}
}
int main(){
int T,kase=0;
scanf("%d",&T);
while(T--){
printf("Case %d:\n",++kase);
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
build(1,n,1);
char cmd[5];
while(scanf("%s",cmd)==1){
if(cmd[0]=='E')break;
int x,y;
scanf("%d%d",&x,&y);
if(cmd[0]=='A'){
add(x,y,1);
}
else if(cmd[0]=='S'){
add(x,-y,1);
}
else if(cmd[0]=='Q'){
printf("%d\n",query(x,y,1));
}
}
}
return 0;
}