#HDU - 1166 - 敌兵布阵
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的责骂的.
Input
第一行一个整数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条命令
Output
对第i组数据,首先输出“Case i:”和回车,
对于每个Query询问,输出一个整数并回车,表示询问的段中的总人数,这个数保持在int以内。
Sample Input
1
10
1 2 3 4 5 6 7 8 9 10
Query 1 3
Add 3 6
Query 2 7
Sub 10 2
Add 6 3
Query 3 10
End
Sample Output
Case 1:
6
33
59
题目链接
题目告诉你一个数N,表示一共有N个工兵营,并告诉你每个工兵营的人数,然后是一系列操作,遇到End结束输入。
这道题目是线段树的基础题目,建立线段树之后,进行单点的更改,然后查询区间和,然后在输入操作方式的时候,我采用了map的方式,其实怎么做都可以,一样效果。
至于其余的注释,就放在代码里面了
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 4e5 + 5; //这个地方个人比较习惯设成4e几,因为这是个树,但应该都一样
int ary[maxn]; //存储每一个工兵营的人数
struct node
{
int l, r, val;
}p[maxn];
//这个函数算是比较重要的,因为他把子节点的信息汇总到了父节点上,
//随着题目的改变这个函数也是需要作出相应改变的,并且有的时候要想全面。
void pushup(int cur)
{
//父节点是两个子节点的和,一直往上传值,就能使根节点是所有人的个数
p[cur].val = p[2 * cur].val + p[2 * cur + 1].val;
}
void build(int l, int r, int cur)
{
int m = (l + r) / 2;
p[cur].val = 0; //这个地方并没什么用,因为后面到了叶节点就会赋值了,但是有的时候,建树的初始值很重要
p[cur].l = l; p[cur].r = r;
//如果到达叶节点就把人数赋给该节点
if(l == r)
{
p[cur].val = ary[l];
return ;
}
//如果不是叶节点,就一直递归找左右孩子,然后知道找到叶节点进行传值
build(l, m, 2 * cur);
build(m + 1, r, 2 * cur + 1);
//将子节点的人数进行汇总,给父节点
pushup(cur);
}
//单点值的更改,找指定的叶节点,跟上面建树时候代码差不多,一直在找叶节点,然后对值做出相应更改
void update(int tar, int val, int cur)//tar是目标位置,即数组的下标,第几个工兵营,
{
int l = p[cur].l, r = p[cur].r;
if(l == r)
{
p[cur].val = p[cur].val + val;
return ;
}
int m = (l + r) / 2;
if(tar <= m) update(tar, val, 2 * cur);
else update(tar, val, 2 * cur + 1);
pushup(cur);
}
//查询区间值
int query(int ql, int qr, int cur)
{
int l = p[cur].l, r = p[cur].r;
//如果一个树的节点所在的区间能够被所求区间完全包含(包括等于),那么就直接返回该区间值
//我在学线段树的时候,看到这里总会担心,他这样的话返回了区间值,可是这个区间比所求区间可能小啊
//后来发现担心有点多余了,因为,他还有兄弟节点,如果他所在区间不够的话,会再去找他的兄弟,最不好
//的情况就是一直找到叶节点
if(ql <= l && qr >= r) return p[cur].val;
int m = (l + r) / 2, res = 0;
//如果不能完全包含,就去找他的左右孩子
if(ql <= m) res += query(ql, qr, 2 * cur);
if(qr > m) res += query(ql, qr, 2 * cur + 1);
return res;
}
int main()
{
int t, n;
scanf("%d", &t);
for(int test = 1; test <= t; test++)
{
scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%d", &ary[i]);
build(1, n, 1);
getchar();
map<string, int> mp;
mp["Query"] = 1;
mp["Add"] = 2;
mp["Sub"] = 3;
mp["End"] = 4;
int first = 1;
while(1)
{
string str;
cin >> str;
if(first) printf("Case %d:\n", test), first = 0;
if(mp[str] == 1)
{
int ql, qr;
scanf("%d%d", &ql, &qr);
printf("%d\n", query(ql, qr, 1));
}
else if(mp[str] == 2)
{
int c, m;
scanf("%d%d", &c, &m);
update(c, m, 1);
}
else if(mp[str] == 3)
{
int c, m;
scanf("%d%d", &c, &m);
update(c, -m, 1); //减去的话,就是加了个负数
}
else break ;
}
}
return 0;
}