题目地址 : http://acm.hdu.edu.cn/showproblem.php?pid=4348
2012多校第五场,1010题。
思路 :
其实很简单,线段树的区间查询、区间求和。
只是两个“查询第 t 次修改时的内容”、“恢复到 t 时刻”操作比较不好想。
我这里利用的是离线法。
先将“修改操作”和“查询操作”分别放入栈和优先队列中。
如果没有遇到 Back 操作,那么我们最后直接将时间 倒着走一遍即可,同时保存结果。
如果遇到了 Back 操作,也不着急,将时间倒着走到 t 时刻,保存这段时间内的询问的结果。
然后继续输入新的数据,再遇到 Back,则再次回到 t 时刻,如此往复,直到不再有 Back,直接倒着走完。
此时,我们已经得到了所有询问的结果。
按照询问的顺序,依次输出即可。
PS :
比赛的时候按照类似这个的思路去敲,TLE了,额,囧。。。
同样也是离线的线段树。
与现在AC的代码相比,比赛时的代码 不同的应该是 Back 部分。
在当时,Back操作 时,我是将 “询问操作” 拿出队列后,待删除了 t 之后的所有“修改操作”之后,又把“询问操作”重新加回队列。。。
于是只要 Back 操作 够多,“询问操作”的数量也够多,那么足够让我 TLE 到屎了。。。
代码改进后,“询问操作” 出了队列之后,就不用再进队列了。
而相应的,每个“修改操作”需要进行两次,一次是区间加数,另一次是区间减数(即销毁自己,相当于退了1个时间)。
所以时间提高了许多。。。
(由于比赛时TLE,所以把STL的优先队列改成了手写堆,但还是TLE。。。 现在这个代码就没把手写堆改回去了,毕竟这个的时间更快嘛,嘿嘿)。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#define ll (v<<1)
#define rr (v<<1|1)
#define tmid (((l+r)>>1))
using namespace std;
const int maxn=1000030;
int n,m,q;
int s[maxn];
__int64 res[maxn];
// 手写堆 , 用于存储【询问操作】,堆顶为时间t ===============
struct Ans{
int l,r,t,i;
Ans(){};
Ans(int a,int b,int c,int d){l=a,r=b,i=c,t=d;}
}Q[maxn];
Ans que[maxn];
void add(int v){
Ans tmp;
while(v>1){
if(que[v].t<=que[v>>1].t) return;
tmp=que[v];
que[v]=que[v>>1];
que[v>>1]=tmp;
v>>=1;
}
}
void change(int v){
int l=v<<1;
Ans tmp;
while(l<=q){
if(l+1<=q && que[l+1].t>que[l].t) l++;
if(que[v].t>=que[l].t) return;
tmp=que[v];
que[v]=que[l];
que[l]=tmp;
v=l;
l=v<<1;
}
}
// 线段树,含有区间更新、查询 ======================
__int64 num[maxn<<2],rec[maxn<<2];
__int64 query(int L,int R,int l,int r,int v){
if(L<=l && r<=R)
return num[v];
if(rec[v]){
rec[ll]+=rec[v];
rec[rr]+=rec[v];
num[ll]+=rec[v]*(tmid-l+1);
num[rr]+=rec[v]*(r-tmid);
rec[v]=0;
}
__int64 res=0;
if(L<=tmid) res+=query(L,R,l,tmid,ll);
if(tmid<R) res+=query(L,R,tmid+1,r,rr);
return res;
}
void update(int L,int R,int d,int l,int r,int v){
if(L<=l && r<=R){
rec[v]+=d;
num[v]+=(__int64)(r-l+1)*d;
return;
}
if(rec[v]){
rec[ll]+=rec[v];
rec[rr]+=rec[v];
num[ll]+=rec[v]*(tmid-l+1);
num[rr]+=rec[v]*(r-tmid);
rec[v]=0;
}
if(L<=tmid) update(L,R,d,l,tmid,ll);
if(tmid<R) update(L,R,d,tmid+1,r,rr);
num[v]=num[ll]+num[rr];
}
void make_tree(int l,int r,int v){
rec[v]=0;
if(l==r){
num[v]=s[l];
return;
}
make_tree(l,tmid,ll);
make_tree(tmid+1,r,rr);
num[v]=num[ll]+num[rr];
}
// back操作,回到 t 时刻 ======================
struct Cge{ // 用栈C[] ,存储 【修改操作】
int l,r,d;
Cge(){};
Cge(int a,int b,int c){l=a,r=b,d=c;}
}C[maxn];
void clr(int t,int tol){
while(tol>t){
while(q>0)
if(que[1].t>=tol){
// cout<<"Q: ( "<<que[1].l<<" , "<<que[1].r<<" ) --- "<<que[1].i<<endl;
res[que[1].i]=query(que[1].l,que[1].r,1,n,1);
// cout<<"res: "<<res[que[1].i]<<endl;
que[1]=que[q--];
change(1);
}
else break;
// cout<<"C: ( "<<C[tol].l<<" , "<<C[tol].r<<" ) --- "<<tol<<endl;
update(C[tol].l,C[tol].r,-C[tol].d,1,n,1);
tol--;
}
}
// 主函数 ====================================
int main(){
int i,j,tol,now,l,r,d,dis,cnt,t,step;
char op[3];
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++)
scanf("%d",&s[i]);
make_tree(1,n,1);
tol=0;
step=0;
q=0;
C[0]=Cge(1,n,0);
while(m--){
scanf("%s",op);
if(op[0]=='C'){
scanf("%d%d%d",&l,&r,&d);
C[++tol]=Cge(l,r,d);
update(l,r,d,1,n,1);
}
else if(op[0]=='Q'){
scanf("%d%d",&l,&r);
que[++q]=Ans(l,r,step++,tol);
add(q);
}
else if(op[0]=='H'){
scanf("%d%d%d",&l,&r,&t);
que[++q]=Ans(l,r,step++,t);
add(q);
}
else if(op[0]=='B'){
scanf("%d",&t);
clr(t,tol);
tol=t;
}
}
clr(-1,tol);
for(i=0;i<step;i++)
printf("%I64d\n",res[i]);
return 0;
}
/*
==================
2 3
0 0
C 1 2 1
B 1
Q 1 2
---------------
2
========================
10 5
0 0 0 0 0 0 0 0 0 0
C 1 10 1
C 1 10 1
B 0
C 1 10 2
H 1 10 1
---------------
20
========================
10 5
1 2 3 4 5 6 7 8 9 10
Q 4 4
Q 1 10
Q 2 4
C 3 6 3
Q 2 4
-----------------
4
55
9
15
========================
2 4
0 0
C 1 1 1
C 2 2 -1
Q 1 2
H 1 2 1
-----------------
0
1
*/