刚开始以为看到这道题是提交,ac的人很多,以为是水题,用最简单的方法操作,超时!
线段树!!
线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
对于线段树中的每一个非叶子节点[a,b],它的左儿子表示的区间为[a,(a+b)/2],右儿子表示的区间为[(a+b)/2+1,b]。因此线段树是平衡二叉树,最后的子节点数目为N,即整个线段区间的长度。
使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度为2N,因此有时需要离散化让空间压缩。
实际应用:(源自百度百科)
最简单的应用就是记录线段有否被覆盖,并随时查询当前被覆盖线段的总长度。那么此时可以在结点结构中加入一个变量int count;代表当前结点代表的子树中被覆盖的线段长度和。这样就要在插入(删除)当中维护这个count值,于是当前的覆盖总值就是根节点的count值了。
另外也可以将count换成bool cover;支持查找一个结点或线段是否被覆盖。 [1] 实际上,通过在结点上记录不同的数据,线段树还可以完成很多不同的任务。例如,如果每次插入操作是在一条线段上每个位置均加k,而查询操作是计算一条线段上的总和,那么在结点上需要记录的值为sum。 这里会遇到一个问题:为了使所有sum值都保持正确,每一次插入操作可能要更新O(N)个sum值,从而使时间复杂度退化为O(N)。 解决方案是 Lazy思想:对整个结点进行的操作,先在结点上做标记,而并非真正执行,直到根据查询操作的需要分成两部分。 根据Lazy思想,我们可以在不代表原线段的结点上增加一个值toadd,即为对这个结点,留待以后执行的插入操作k值的总和。对整个结点插入时,只更新sum和toadd值而不向下进行,这样时间复杂度可证明为O(logN)。 对一个toadd值为0的结点整个进行查询时,直接返回存储在其中的sum值;而若对toadd不为0的一部分进行查询,则要更新其左右子结点的sum值,然后把toadd值传递下去,再对这个查询本身,左右子结点分别 递归 下去。时间复杂度也是O(logN)。
#include <iostream> using namespace std; struct Node { int l,r,mid,max; }node[600000]; void init(int a,int b,int n) { node[n].l=a; node[n].max=-1; node[n].r=b; node[n].mid=(a+b)/2; if(a+1==b) return; init(a,(a+b)/2,n*2); init((a+b)/2,b,2*n+1); } void update(int pos,int value,int n) { if(value>node[n].max) node[n].max=value; if((node[n].l+1)==node[n].r) return; if(pos<node[n].mid) { update(pos,value,2*n); } if(pos>=node[n].mid) { update(pos,value,2*n+1); } } int query(int a,int b,int n) { if(node[n].l==a && node[n].r==b) return node[n].max; if(a<node[n].mid) { if(b<=node[n].mid) { return query(a,b,2*n); } else { return max(query(a,node[n].mid,2*n),query(node[n].mid,b,2*n+1)); } } else { return query(a,b,2*n+1); } } int main() { int n,m,t,a,b,i; char ch; while(cin >>n>>m) { init(1,n+1,1); for(i=1;i<=n;i++) { scanf("%d",&t); update(i,t,1); } for(i=1;i<=m;i++) { getchar(); cin >>ch; cin >>a>>b; if(ch=='Q') cout <<query(a,b+1,1)<<endl;//注意是b+1而不是b。因为线段树是一个左开右闭的的区间。 else update(a,b,1); } } return 0; }