士兵杀敌----树状数组的应用

刚接触数组只认为是一种数据结构,能够方便的进行数组的求和,做了一道名为《士兵杀敌》的一系列题目,感觉以前学了“假树状数组“尴尬”。最大的感悟就是不要自己瞎改功能函数。

首先说一下树状数组的几个关键功能函数:


int lowbit(int x) {
    return x&(-x);
}
void add(int x, int n, int v) { //当对原数组下标为x的元素该变大小v时,应将所有树形数组关于它的元素改变v 
	while(x < n) {
		T[x] += v;
		x += lowbit(x); //这样的计算结果为所有涉及最初x元素的树状数组下标 
	}
}
int sum(int x) { //求前x项和 
	int sum = 0;
	while(index > 0) { //假设树状数组下标从1开始
		sum + T[index];
		x -=  lowbit(x); 
	}
}







下面是《士兵杀敌》一到五的题目链接:

http://acm.nyist.net/JudgeOnline/problem.php?pid=108

http://acm.nyist.net/JudgeOnline/problem.php?pid=116

http://acm.nyist.net/JudgeOnline/problem.php?pid=119

http://acm.nyist.net/JudgeOnline/problem.php?pid=123

http://acm.nyist.net/JudgeOnline/problem.php?pid=228


士兵杀敌一:

   简单的一个树状数组就能解决了,算是熟悉一下树状数组的功能和代码!(貌似用前n项和更简单QAQ)

士兵杀敌二:

   除了第一次初始化树状数组以外,以后要不断修改树状数组。

   使用add函数对树状数组进行维护,不断修改与当前士兵杀敌数相关的所有树状数组中的元素,最后询问m到n的总杀敌数量,只需前n项和减去前m项和即可。

   如果询问的是单个人的杀敌数量只需前n项和减去前n-1项和。

士兵杀敌三:

求最大值和最小值之差,只需算出每个阶段的最大最小值即可。

这里采用RMQ算法(动态规划思想)。详情请看:

http://www.cppblog.com/reiks/archive/2009/08/28/94629.aspx

士兵杀敌四:

这道题目是几个人平分军功,只需在树状数组中模拟求和函数,关键点在于不能重复。

对m-n的求和过程经过的那些树状数组元素加上军功,这样就不会有重复了。

而且还要注意,sum求和时是对前x项和,所以先要对前m-1项加上-v,再让前n项加上v。求最后结果的时候,因为是问某个人的杀敌数,所以采用类似add函数的方式求一下所有涉及到该士兵的树状数组和。

  

void Plus(int n,int value) //前n项每项增加value
11. {
12. while(n>0)
13. {
14. data[n]+=value;
15. n-=LowBit(n);
16. }
17. }
18. int Get(int n) //获取每个位置的值
19. {
20. int sum=0;
21. while(n<=Max)
22. {
23. sum+=data[n];
24. n+=LowBit(n);
25. }
26. return sum;
27. }


      
      
士兵杀敌五:

这道题与前一道题的区别在于是m-n之间所有人都加上v。

原来我想的是只需加上将v变成lowbit(x)*v就行了,然后发现不对劲;

假如长度是8,1-4人需要加军功10,那就是T[4]+40,假如查1-8人的总军功的话,由于T[8]本身就是前8项和,直接就是0了!!!这明显不对啊,所以这题应该用第二题思想,需要对m-n之间的所有人都用add函数维护一下(第四题因为是询问某一个人的军功,所以没有这个“命门”),这样效率就很低了,不能体现出树状数组的“强劲威力”。

后来看了网上其他的人解题报告,知道了一个新的类似树状数组的一个算法:

对每一组输入数据来说,在sum数组中进行标记开始和结束(让sum[m]=-Ai,sum[n]=Ai),所有数据输入完毕后,求一下当前的后n项和,这样的sum刚好是每个人的军功(amazing)!!!

这个算法的关键点在将正军功放在后面,负军功放在前面,而且是倒着求后n项和,(倒过来也是可以的)这就相当于对m之前的所有人先加上-Ai,再让n之前所有人加上Ai。

 代码直接附上:

01. #include <cstdio>
02.  
03. #define SIZE 1000010
04.  
05. int sum[SIZE];
06.  
07. int main()
08. {
09. int N,C,Q;
10. scanf("%d%d%d",&N,&C,&Q);
11. int i;
12. for (i = 0;i < C;i++)
13. {
14. int Mi,Ni,Ai;
15. scanf("%d%d%d",&Mi,&Ni,&Ai);
16. sum[Mi - 1] += -Ai;
17. sum[Ni] += Ai;
18. }
19. for (i = N;i > 0;i--)
20. {
21. sum[i] += sum[i+1];
22. }
23. sum[0] = 0;
24. for (i = 1;i <= N;i++)
25. {
26. sum[i] += sum[i-1];
27. sum[i] %= 10003;
28. }
29. for (i = 0;i < Q;i++)
30. {
31. int m,n;
32. scanf("%d%d",&m,&n);
33. printf("%d\n",(sum[n] - sum[m-1] + 10003)%10003);
34. }
35. return 0;
36. }











  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值