士兵杀敌三
士兵杀敌(三)
- 描述
-
南将军统率着N个士兵,士兵分别编号为1~N,南将军经常爱拿某一段编号内杀敌数最高的人与杀敌数最低的人进行比较,计算出两个人的杀敌数差值,用这种方法一方面能鼓舞杀敌数高的人,另一方面也算是批评杀敌数低的人,起到了很好的效果。
所以,南将军经常问军师小工第i号士兵到第j号士兵中,杀敌数最高的人与杀敌数最低的人之间军功差值是多少。
现在,请你写一个程序,帮小工回答南将军每次的询问吧。
注意,南将军可能询问很多次。
- 输入
- 只有一组测试数据
第一行是两个整数N,Q,其中N表示士兵的总数。Q表示南将军询问的次数。(1<N<=100000,1<Q<=1000000)
随后的一行有N个整数Vi(0<=Vi<100000000),分别表示每个人的杀敌数。
再之后的Q行,每行有两个正正数m,n,表示南将军询问的是第m号士兵到第n号士兵。 - 输出
- 对于每次询问,输出第m号士兵到第n号士兵之间所有士兵杀敌数的最大值与最小值的差。
- 样例输入
-
5 2 1 2 6 9 3 1 2 2 4
- 样例输出
-
1 7
http://blog.csdn.net/zearot/article/details/48299459
线段树用途: 要用于高效解决连续区间的动态查询问题
1.插入点型
对于这种线段树,通常是向线段树中插入点,即对应一个叶子节点的信息,而线段树中所有节点也都是记录的关于以该点为根的子树中已插入的点的统计信息,通常是问线段树中某个区间对叶子节点的统计信息(如最大值,最小值,和)
2.线覆盖型
对于这种线段树,与第三种有一下共同特点:所有询问和插入操作都是以区间为单位的,每次都是对一个区间进行操作。每个节点通常会用一个变量来记录以它为跟的子树的相关信息,在回溯过程中更新此变量。当操作的区间能完整的覆盖一个节点对应的区间时直接对该节点操作,不再向下操作。
这种线段树还有其独有的特点:当操作一个短线段时,这个短线段在线段树中由上至下运行到自己对应的位置,这个过程中会经过若干个对应长线段的节点,在经过长线段时,要把长线段的节点中的信息移动到其两个直接子节点中,然后再继续向下走。这样可以保证区间信息存储位置的在树中的纵向专一性,即树中节点的直系血亲之间只能有一个点记录信息。
原因如下:在这种线段树的题通常具有以下性质:(1)新插入线段与旧的线段重叠的部分的信息如果纵向分布,不存储在同一个节点中,则在回溯统计子树信息过程很难计算。(2)新插入的线段与旧线段的重叠部分可以只保存新线段信息,这样才能插入过程中完整覆盖一个节点时不用向下操作。
3.线保留型
这种线段树除了与第二种线段树的共同特点外,还有一个重要特性就是即使旧线段与新线段重叠了旧线段中的信息也仍然有意义,或者要求插入的线段必须保持在其插入的位置不向下迭代。
其中保持位置的一种典型情况就是有删除操作。删除并不是随意的删除,每次删除的线段与原来插入的线段相对应,只删除那些曾经插入过的线段。这种通常我们为了删除时候方便,在短线段向下运行经过长线段的过程中不会把长线段向下迭代,因为长线段是要被删除的,如果向下迭代删除时就没有办法对长线段原来的存储节点进行操作,只能对其许许多多的后代节点中的一些(那些被迭代到了的节点)进行操作,复杂度极高。这种线段树的题通常信息纵向分布也是可以回溯计算的。
指针实现线段树
结构体数组实现线段树
#include<stdio.h> //#include<algorithm> //using namespace std; #define INF 0x3f3f3f3f struct tt { int l,r,sum,ma,mi; }t[300200]; int e=-1,s=INF; void pushup(int o) { t[o].sum=t[o*2].sum+t[o*2+1].sum; t[o].ma=t[o*2].ma>t[o*2+1].ma?t[o*2].ma:t[o*2+1].ma; t[o].mi=t[o*2].mi<t[o*2+1].mi?t[o*2].mi:t[o*2+1].mi; } void build(int o,int l,int r) { t[o].l=l;t[o].r=r; if(l==r) { int p;scanf("%d",&p); t[o].sum=t[o].mi=t[o].ma=p; return ; } int mid=(l+r)/2; build(o*2,l,mid); build(o*2+1,mid+1,r); pushup(o); } void find1(int o,int l,int r) { if(l==t[o].l&&r==t[o].r) { e=e>t[o].ma?e:t[o].ma; s=s<t[o].mi?s:t[o].mi; return ; } int mid=(t[o].l+t[o].r)/2; if(mid>=r) find1(o*2,l,r); else if(l>mid) find1(o*2+1,l,r); else { find1(o*2,l,mid); find1(o*2+1,mid+1,r); } } int main() { int n,m; while(scanf("%d%d",&n,&m)!=EOF) { build(1,1,n); for(int i=0;i<m;i++) { int a,b; scanf("%d%d",&a,&b); e=-1,s=INF; find1(1,a,b); printf("%d\n",e-s); } } return 0; }
RMQ算法:RMQ(Range Minimum/Maximum Query),即区间最值查询,是指这样一个问题:对于长度为n的数列A,回答若干询问RMQ(A,i,j)(i,j<=n),返回数列A中下标在i,j之间的最小/大值
#include<stdio.h> #include<math.h> #include<iostream> using namespace std; int maxsum[20][100001]; int minsum[20][100001]; void RMQ(int num) //预处理->O(nlogn) { for(int i = 1; i != 20; ++i) for(int j = 1; j <= num; ++j) if(j + (1 << i) - 1 <= num) { maxsum[i][j] = max(maxsum[i - 1][j], maxsum[i - 1][j + (1 << i >> 1)]); //优化2 minsum[i][j] = min(minsum[i - 1][j], minsum[i - 1][j + (1 << i >> 1)]); } } int main() { int n,i,j,x,u,v,q; scanf("%d %d",&n,&q); for(i=1;i<=n;i++) { scanf("%d",&x); maxsum[0][i] = x; minsum[0][i] = x; } RMQ(n); for(i=1;i<=q;i++) { scanf("%d %d",&u,&v); int k = log2( v - u + 1); printf("%d\n",max(maxsum[k][u], maxsum[k][v-(1<<k)+1]) - min(minsum[k][u], minsum[k][v-(1<<k)+1])); } //1<<k ji 2^k 但是 2^k无法编译 return 0; }