最近在看《算法竞赛入门经典训练指南》, 感觉以前的想法几乎完全是错的,模板并不一定能直接套。
最近在看线段树,才知道线段树每个节点的附加信息才是重头戏,因此完全套模板是不可行的。但是思想方法可以借鉴,并在此基础上 加以改进。
1.点修改
给出一个有n个元素的数组A[1],A[2],……A[n]。任务是设计一个数据结构,支持以下两种操作:
①update(x,v):把A[x]修改为v。
②Query(L,R):计算min{A[L],A[L+1],……A[R]}。
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <set>
#include <vector>
#include<cmath>
#include<bitset>
#include<sstream>
using namespace std;
#define INF 0x7fffffff
#define maxn 100005
#define maxnode 200005
//线段树的节点编号是1-n
int ql,qr;//查询[ql,qr]中的最小值
int p,v;//修改,A[p]=v;
struct IntervalTree
{
int minv[maxnode];
//minv[o]表示节点o所对应区间中所有元素的最小值
//查询[ql,qr]中的最小值
int query(int o,int L,int R){
int M=L+(R-L)/2;
int ans=INF;
if(ql<=L&&R<=qr) return minv[o];//当前节点完全包含在查询区间内
if(ql<=M) ans=min(ans,query(o*2,L,M));//往左走
if(qr>M) ans=min(ans,query(o*2+1,M+1,R));
return ans;
}
//修改:A[p]=v;
void update(int o,int L,int R){
int M=L+(R-L)/2;
if(L==R) minv[o]=v;//叶节点直接更新minv
else {
//L<R
//先递归更新左子树或者右子树
if(p<=M){
update(2*o,L,M);
}
else{
update(2*o+1,M+1,R);
}
//计算本节点的minv
minv[0]=min(minv[2*o],minv[2*o+1]);
}
}
};
IntervalTree tree;
int main()
{
int n,m;
while(scanf("%d%d",&n,&m)==2){
memset(&tree,0,sizeof(tree));
int op;
for(int i=1;i<=n;i++){
scanf("%d",&v);
p=i;
tree.update(1,1,n);//建树的过程
}
while(m--){
scanf("%d",&op);
if(op==1){
scanf("%d%d",&p,&v);
tree.update(1,1,n);//修改树节点
}
else{
scanf("%d%d",&ql,&qr);//修改查询区间
printf("%d\n",tree.query(1,1,n));
}
}
}
return 0;
}
2.区间修改
1.快速区间操作1
1.操作:
给出一个n个元素的数组A[1],A[2],……A[n],你的任务是设计一个数据结构支持以下两种操作:
Add(L,R,v):把A[L],A[L+1],……A[R]的值全部增加v;
Query(L,R):计算子序列A[L],A[L+1],……A[R]的元素和,最小值和最大值。
点修改只会影响logn个结点,但是区间修改,在最坏情况下会影响树中的所有结点。前面讲过,任意区间都能分解成不超过2h个不想交的区间的并。利用这个结论,可以化整为零。
2.维护结点的信息
前面已经讲过,如果仍然用sum[o]表示结点o对应的区间中所有数之和,则add操作最坏情况下可能会修改所有的sum。解决办法是把sum[o]的定义改成“如果只执行结点o及其子孙结点中的add操作,结点o对应区间中所有数之和”。这样附加信息可以方便的维护,而且每个原始add所影响的结点数目变成了O(h)。代码如下:
//维护信息
//维护结点o,它对应的区间是[L,R]
void maintain(int o,int L,int R){
int lc=2*o,rc=2*o+1;
sumv[o]=minv[o]=maxv[o]=0;
if(R>L){
//考虑左右子树
sumv[o]=sumv[lc]+sumv[rc];
minv[o]=min(minv[lc],minv[rc]);
maxv[o]=max(maxv[lc],maxv[rc]);
}
//考虑add操作
minv[o]+=addv[o];
maxv[o]+=addv[o];
sumv[o]+=addv[o]*(R-L+1);
}
3.修改操作
在进行add操作时,哪些结点需要调用上述maintain函数呢?很简单,递归访问到的所有结点全部要调用,并且是在递归返回后调用。代码如下:
//修改操作
void update(int o,int L,int R){
int lc=2*o,rc=2*o+1;
if(qL<=L&&R<=qR){
//递归边界
addv[o]+=v;//累加边界的add值
}
else{
int M=L+(R-L)/2;
if(qL<=M) update(lc,L,M);
if(qR>M) update(rc,M+1,R);
}
//递归结束前重新计算本结点的附加信息
maintain(o,L,R);
}
4.查询操作
仍然是把查询区间递归分解成若干个不相交的子区间,把各个子区间的查询结果加以合并,但需要注意的是每个边界区间的结果不能直接用,还得考虑祖先结点对它的影响。
为了方便,我们在递归函数中增加一个参数,表示当前区间的所有祖先结点的add之和。代码如下:
void query(int o,int L,int R,int add){
if(qL<=L&&R<=qR){
//递归边界:用边界区间的附加信息更新答案
_sum+=sumv[o]+add*(R-L+1);
_min=min(_min,minv[o]+add);
_max=max(_max,maxv[o]+add);
}
else{
//不递归统计,累加参数add
int M=L+(R-L)/2;
if(qL<=M) query(2*o,L,M,add+addv[o]);
if(qR>M) query(2*o+1,M+1,R,add+addv[o]);
}
}
5.完整代码(模板)
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <set>
#include <vector>
#include<cmath>
#include<bitset>
#include<sstream>
#include<stack>
using namespace std;
#define INF 0x7fffffff
typedef long long ll;
typedef pair<int,int> P;
const int maxnode=1<<17;
int n,m;
//编号从1到n
int _min,_max,_sum;//全局变量,目前位置的最小值、最大值以及累加和
int op,qL,qR,v;
//m个操作
//1 L R v
//2 L R
struct IntervalTree
{
int sumv[maxnode],minv[maxnode],maxv[maxnode],addv[maxnode];
//维护信息
//维护结点o,它对应的区间是[L,R]
void maintain(int o,int L,int R){
int lc=2*o,rc=2*o+1;
sumv[o]=minv[o]=maxv[o]=0;
if(R>L){
//考虑左右子树
sumv[o]=sumv[lc]+sumv[rc];
minv[o]=min(minv[lc],minv[rc]);
maxv[o]=max(maxv[lc],maxv[rc]);
}
//考虑add操作
minv[o]+=addv[o];
maxv[o]+=addv[o];
sumv[o]+=addv[o]*(R-L+1);
}
//修改操作
void update(int o,int L,int R){
int lc=2*o,rc=2*o+1;
if(qL<=L&&R<=qR){
//递归边界
addv[o]+=v;//累加边界的add值
}
else{
int M=L+(R-L)/2;
if(qL<=M) update(lc,L,M);
if(qR>M) update(rc,M+1,R);
}
//递归结束前重新计算本结点的附加信息
maintain(o,L,R);
}
void query(int o,int L,int R,int add){
if(qL<=L&&R<=qR){
//递归边界:用边界区间的附加信息更新答案
_sum+=sumv[o]+add*(R-L+1);
_min=min(_min,minv[o]+add);
_max=max(_max,maxv[o]+add);
}
else{
//不递归统计,累加参数add
int M=L+(R-L)/2;
if(qL<=M) query(2*o,L,M,add+addv[o]);
if(qR>M) query(2*o+1,M+1,R,add+addv[o]);
}
}
};
IntervalTree tree;
int main()
{
while(scanf("%d%d",&n,&m)==2){
memset(&tree,0,sizeof(tree));
while(m--){
scanf("%d%d%d",&op,&qL,&qR);
if(op==1){
scanf("%d",&v);
tree.update(1,1,n);
}
else{
_sum=0,_min=INF,_max=-INF;
tree.query(1,1,n,0);
printf("%d %d %d\n",_sum,_min,_max);
}
}
}
return 0;
}
懂了上述内容,就可以解决poj3468了,这是一道模板题,又是G++超时,C++AC
http://poj.org/problem?id=3468
2.快速序列操作2
1.操作
给定一个有n个元素的数组A[1],A[2],……A[n].任务是设计一个数据结构,支持以下两种操作:
1.set(L,R,v):把A[L],A[L+1],……A[R]全部修改为v(v>=0)
2.query(L,R):计算子序列A[L],A[L+1],……A[R]的元素和、最小值和最大值。
2.代码
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <set>
#include <vector>
#include<cmath>
#include<bitset>
#include<sstream>
#include<stack>
using namespace std;
#define INF 0x7fffffff
typedef long long ll;
const int maxnode=1<<17;
int _sum,_min,_max,op,qL,qR,v;
struct IntervalTree
{
int sumv[maxnode],maxv[maxnode],minv[maxnode],setv[maxnode];
//维护信息
void maintain(int o,int L,int R){
int lc=2*o,rc=2*o+1;
if(R>L){
sumv[o]=sumv[lc]+sumv[rc];
minv[o]=min(minv[lc],minv[rc]);
maxv[o]=max(maxv[lc],maxv[rc]);
}
if(setv[o]>=0){
minv[o]=maxv[o]=setv[o];
sumv[o]=setv[o]*(R-L+1);
}
}
//标记传递
void pushdown(int o){
int lc=2*o,rc=2*o+1;
if(setv[o]>=0){
//本结点有标记才传递。注意本题中set值非负
setv[lc]=setv[rc]=setv[o];
setv[o]=-1;//清除本结点标记
}
}
//更新信息
void update(int o,int L,int R){
int lc=2*o,rc=2*o+1;
if(qL<=L&&R<=qR){
//标记修改
setv[o]=v;
}
else{
pushdown(o);
int M=L+(R-L)/2;
if(qL<=M) update(lc,L,M); else maintain(lc,L,M);
if(qR>M) update(rc,M+1,R); else maintain(rc,M+1,R);
}
maintain(o,L,R);
}
void query(int o,int L,int R){
if(setv[o]>=0){
//递归边界1:有set标记
_sum+=setv[o]*(min(R,qR)-max(L,qL)+1);
_min=min(_min,setv[o]);
_max=max(_max,setv[o]);
}
else if(qL<=L&&qR>=R){
//递归边界2:边界区间
//此边界区间没有被任何set操作影响
_sum+=sumv[o];
_min=min(_min,minv[o]);
_max=max(_max,maxv[o]);
}
else{
//递归统计
int M=L+(R-L)/2;
if(qL<=M) query(2*o,L,M);
if(qR>M) query(2*o+1,M+1,R);
}
}
};
IntervalTree tree;
int main()
{
int n,m;
while(scanf("%d%d",&n,&m)==2){
memset(&tree,0,sizeof(tree));
memset(tree.setv,-1,sizeof(tree.setv));
tree.setv[1]=0;
while(m--){
scanf("%d%d%d",&op,&qL,&qR);
if(op==1){
scanf("%d",&v);
tree.update(1,1,n);
}
else{
_sum=0;_min=INF;_max=-INF;
tree.query(1,1,n);
printf("%d %d %d\n",_sum,_min,_max);
}
}
}
return 0;
}