2019南昌网络赛-I. Yukino With Subinterval 线段树套树状数组,CDQ分治
TMD。。。这题卡内存卡的真优秀。。。
所以以后还是别用主席树的写法。。。不然怎么死的都不知道。。。
树套树中,主席树方法开权值线段树。。。会造成空间的浪费。。。这道题内存卡的很紧。。。
由于树套树已经不需要持久化了,直接动态开点就完事了。。。用主席树方法开过不去,要么超内存,要么越界。。。
大概思路。。。这题要求的[L,R]区间内,满足x<=a[i]<=y的连续的段数,
这题大概是个套路题,我们很容易想到,我们把连续的区间变成单点,把一段区间,类似1 1 1 3 5 变成 1 0 0 3 5 这样我们
只需要维护区间内部,在某个范围内数字的个数,这样的求法有个很显然的弊端如果1 0 0 3 5 序列, 我们求的区间是 2到5
这样求出来的答案是2,但是答案是3,究其原因,是我们b[l]端点出了问题,如果b[l]是0,我们求出来的就比答案少一个,那
么如何求出答案呢???很简单,可以先求出a[l+1]-a[r]的个数,这个算出来的是肯定不准确的,在b[l+1]=0的时候,在其他的
情况下正确的,如果b[l+1]=0 或者b[l+1]!=0,我们只要判断a[l]是否满足,如果满足条件就+1,这样就保证b[l+1]的情况,并且
在b[l+1]!=0的情况下,也是正确的。
考虑修改,单点更新,如果当前点是a[l]==a[l-1]那么a[l]将变成一个新的左端点。
如果修改后,a[l]==a[l+1]那么a[l+1]将不再是一个左端点,需要舍去。
#include<iostream> #include<stdio.h> #include<string.h> #include<algorithm> using namespace std; const int maxx = 2e5+6; struct node{ int l,r; int cnt; }tree[maxx*220]; int cnt,n,cnt1,cnt2; int root[maxx],a[maxx],trl[maxx],trr[maxx]; void inserts(int &now,int l,int r,int pos,int w){ if(!now)now=++cnt; tree[now].cnt+=w; if (l==r){ return ; } int mid=(l+r)>>1; if (pos<=mid){ inserts(tree[now].l,l,mid,pos,w); }else{ inserts(tree[now].r,mid+1,r,pos,w); } } int lowbit(int x){ return x&(-x); } void add(int x,int w){ for(int i=x;i<=n;i+=lowbit(i)){ inserts(root[i],1,n,a[x],w); } } int query(int l,int r,int ql,int qr){ if (r<ql || l>qr){ return 0; } int tmpl[1000],tmpr[1000]; int s=0,mid=(l+r)>>1,sum=0; for(int i=1;i<=cnt1;i++)s-=tree[trl[i]].cnt,tmpl[i]=trl[i]; for(int i=1;i<=cnt2;i++)s+=tree[trr[i]].cnt,tmpr[i]=trr[i]; if (ql<=l && r<=qr){ return s; } if (mid>=ql){ for (int i=1;i<=cnt1;i++){ trl[i]=tree[tmpl[i]].l; } for (int i=1;i<=cnt2;i++){ trr[i]=tree[tmpr[i]].l; } sum+=query(l,mid,ql,qr); } if (mid<qr){ for (int i=1;i<=cnt1;i++){ trl[i]=tree[tmpl[i]].r; } for (int i=1;i<=cnt2;i++){ trr[i]=tree[tmpr[i]].r; } sum+=query(mid+1,r,ql,qr); } return sum; } int main(){ int m; scanf("%d%d",&n,&m); cnt=cnt1=cnt2=0; for (int i=1;i<=n;i++){ scanf("%d",&a[i]); if (a[i]!=a[i-1])add(i,1); } int op; int pos,v,l,r,x,y; while(m--){ scanf("%d",&op); if(op==1){ scanf("%d%d",&pos,&v); if (a[pos]==v)continue; if(a[pos]!=a[pos-1]){ add(pos,-1); } if(a[pos]==a[pos+1]){ add(pos+1,1); }else if (a[pos+1]==v){ add(pos+1,-1); } a[pos]=v; if (a[pos]!=a[pos-1]){ add(pos,1); } }else { scanf("%d%d%d%d",&l,&r,&x,&y); cnt1=cnt2=0; int f=0; for (int i=l;i;i-=lowbit(i)){ trl[++cnt1]=root[i]; } for (int i=r;i;i-=lowbit(i)){ trr[++cnt2]=root[i]; } if (a[l]>=x && a[l]<=y)f++; printf("%d\n",query(1,n,x,y)+f); } } return 0; }
CDQ分治,分成三维偏序问题,第一维时间,也就是构造和询问顺序,第二维度坐标,第三维度值域,按照三维偏序,进行处理,把询问排序即可。
#include<iostream> #include<stdio.h> #include<string.h> #include<algorithm> using namespace std; const int maxx = 4e5+6; const int maxn=2e5+9; struct node{ int op,f,l,x,id; ///操作类型 增加值 位置 值域 答案编号 node(){} node(int op,int f,int l,int x):op(op),f(f),l(l),x(x){}; node(int op,int f,int l,int x,int id):op(op),f(f),l(l),x(x),id(id){}; bool operator < (const node &s) const{ return l<s.l; } }q[maxx*2]; int a[maxx]; int ans[maxx]; int sum[maxx]; int n; ///BIT部分 int lowbit(int x){ return x&(-x); } void add(int x,int w){ for (int i=x;i<=maxn;i+=lowbit(i)){ sum[i]+=w; } return ; } int getsum(int x){ int s=0; for (int i=x;i;i-=lowbit(i)){ s+=sum[i]; } return s; } void cdq(int l,int r){ if (l==r){ return; } int mid=(l+r)>>1; cdq(l,mid); cdq(mid+1,r); sort(q+l,q+mid+1); sort(q+mid+1,q+r+1); int i=l,j=mid+1; while(i<=mid && j<=r){ ///右边的操作对左边没有影响 if (q[j].op==1){ j++;continue; } while(i<=mid && q[i].l<=q[j].l){ ///左边的询问对右边没有影响 if (q[i].op==1)add(q[i].x,q[i].f); i++; } ///查询小于等于q[j].x的个数,比乘以操作类型 ans[q[j].id]+=q[j].f*getsum(q[j].x); j++; } while(j<=r){ if (q[j].op==1){j++;continue;} ans[q[j].id]+=q[j].f*getsum(q[j].x); j++; } i=l,j=mid+1; /**清空**/ while(i<=mid && j<=r){ if (q[j].op==1){ j++; continue; } while(i<=mid && q[i].l<=q[j].l){ if (q[i].op==1)add(q[i].x,-q[i].f); i++; } j++; } } int main(){ int m; scanf("%d%d",&n,&m); memset(ans,0,sizeof(ans)); int tot=0; ///当成插入 for (int i=1;i<=n;i++){ scanf("%d",&a[i]); if (a[i]!=a[i-1])q[++tot]=node(1,1,i,a[i]); } int pos,v,l,r,x,y,op; int cnt=0; for (int i=1;i<=m;i++){ scanf("%d",&op); if (op==1){ scanf("%d%d",&pos,&v); if (a[pos]==v)continue; ///如果修改位置不等于前面一个,那么这个点是左端点,去掉需要在pos删除值a[pos] if (a[pos]!=a[pos-1]){ q[++tot]=node(1,-1,pos,a[pos]); } ///如果当前值是等前面一个值的,那么当前位置修改后,后面一个值,变成左端点,所以+1 if (a[pos]==a[pos+1]){ q[++tot]=node(1,1,pos+1,a[pos+1]); ///如果后一个位置和前面一个值不同,代表右边一个是左端点,但是如果加入的值是等于这个值,那么需要减去 }else if (a[pos+1]==v){ q[++tot]=node(1,-1,pos+1,a[pos+1]); } a[pos]=v; ///修改以后,如果不等于前面一个值,那么还需要更新 if (a[pos]!=a[pos-1]) q[++tot]=node(1,1,pos,a[pos]); }else { cnt++; scanf("%d%d%d%d",&l,&r,&x,&y); if (a[l]<=y && a[l]>=x){ ans[cnt]=1; } l++; if (l>r)continue; ///把二维区间询问拆出来 ///询问<=r && <=y的个数 q[++tot]=node(2,1,r,y,cnt); ///询问<=y && <=l-1的个数 if (l>1)q[++tot]=node(2,-1,l-1,y,cnt); ///询问<=x-1 && <=r的个数 if (x>1)q[++tot]=node(2,-1,r,x-1,cnt); ///询问<=x-1 && <=l-1的个数 if (l>1 && l>1)q[++tot]=node(2,1,l-1,x-1,cnt); } } cdq(1,tot); for (int i=1;i<=cnt;i++){ cout<<ans[i]<<endl; } return 0; }
有不懂欢迎咨询
QQ:1326487164(添加时记得备注)