P3374 【模板】树状数组 1(cdq)
cdq分治
刚学了cdq分治(dyf神犇强力安利下),发现可以做这种题,当然是来试水了(逃
cdq好像只能离线的样子
以下是摘录的几句:
- 在合并的时候,我们只处理左区间的修改,只统计右区间的查询
因为左区间的修改一定可以影响右区间的查询
这就体现出了CDQ分治的基本思想了 - 我们把所有操作都记录到了一个数组中,所以数组的大小至少要开到500000*3
过程: 按操作顺序读入--->按位置从小到大归并排序(这样可以保证每个询问操作进行前,都只有位置更前的修改操作被执行)--->(蓝后就没了)
#include<iostream> #include<cstdio> #include<cstring> #include<cctype> using namespace std; template <typename T> inline void read(T &x){ char c=getchar(); x=0; bool f=1; while(!isdigit(c)) f= !f||c=='-' ? 0:1,c=getchar(); while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar(); x= f? x:-x; } int wt[50]; template <typename T> inline void output(T x){ if(!x) {putchar(48); return;} if(x<0) putchar('-'),x=-x; int l=0; while(x) wt[++l]=x%10,x/=10; while(l) putchar(wt[l--]+48); }
//-----可无视------ struct data{ int id,v,opt; //id:操作所在位,v:视情况而定 opt:操作 bool operator < (const data &tmp) const{ return id<tmp.id||(id==tmp.id&&opt<tmp.opt); //①位置从小到大,②操作先修改后查询 } }a[1500002],b[1500002]; //注意开3倍 int n,m,cnt,tot,ans[500002]; //cnt:操作数 tot:询问数 inline void cdq(int l,int r){ //cdq分治(差不多就是归并排序了) if(l==r) return ; int mid=l+((r-l)>>1); cdq(l,mid); cdq(mid+1,r); int t1=l,t2=mid+1,sum=0; for(int i=l;i<=r;++i){ if((t1<=mid&&a[t1]<a[t2])||t2>r){ if(a[t1].opt==1) sum+=a[t1].v; b[i]=a[t1++]; }else{ if(a[t2].opt==2) ans[a[t2].v]-=sum; else if(a[t2].opt==3) ans[a[t2].v]+=sum; b[i]=a[t2++]; } } for(int i=l;i<=r;++i) a[i]=b[i]; } int main(){ read(n); read(m); int q1,q2; for(int i=1;i<=n;++i) read(a[++cnt].v),a[cnt].id=i,a[cnt].opt=1; //把初始序列当成修改操作 for(int i=1;i<=m;++i){ read(a[++cnt].opt); read(q1); read(q2); if(a[cnt].opt==1) a[cnt].id=q1,a[cnt].v=q2; else{ //查询分为2个操作:左端点减前缀和,右端点加前缀和 a[cnt].id=q1-1; a[cnt].v=++tot; //左端点操作 a[++cnt].id=q2; a[cnt].v=tot; a[cnt].opt=3; //右端点 } } cdq(1,cnt); for(int i=1;i<=tot;++i) output(ans[i]),putchar('\n'); return 0; }