1 条题解
-
0
自动搬运
来自洛谷,原作者为

I_AM_HelloWord
**搬运于
2025-08-24 21:47:49,当前版本为作者最后更新于2018-01-27 21:13:12,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
此题有三种做法:
1.平衡树套线段树
2.线段树套线段树
3.整体二分
这里介绍一下后两种做法。
我先写了个200+行的线段树套平衡树(
注意顺序),然后效率是,简直爆炸。2.线段树套线段树
为什么线段树套平衡树是的呢?因为我们需要查询的是权值,然而我外层是线段树,所以先二分答案,然后分解区间,到了各个平衡树时,又相当于二分了一次位置,实在是没必要。
因此,这里,我们的线段树套线段树,确切的说应该是权值线段树套区间线段树。
外层是权值线段树,实质上就是一个二分答案的过程,然后运用类似归并树和主席树的思想,我们设询问区间为,答案区间为,取其,然后计算在左儿子和询问区间中的数的个数,那么我们就可以判断答案是大于还是小于的了。而在权值线段树的每一个节点上都建一个区间线段树,来维护该权值在所有区间上的出现次数,然后维护一个区间和就好了。
还有就是防止MLE,在区间线段树上搞个动态开点就好了。
算法设计基本上就是这样了,具体细节就看代码了。
参考代码:
#include<cstdio> #include<algorithm> #include<cstring> #include<vector> #include<iostream> #include<ctime> #define rint register int using namespace std; typedef long long ll; const int inf = 0x7fffffff; const int N=5e4+5; const int Tree=N*400; int n,m,node=0,totn; int ql[N],qr[N],op[N],b[N],k[N]; int rt[N<<2],tag[Tree]; ll sz[Tree]; struct Tnode{ int L,R; }T[Tree]; inline int read(){ int x=0,f=1;char ch=getchar(); while (ch<'0' || ch>'9'){if (ch=='-')f=-1;ch=getchar();} while ('0'<=ch && ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();} return x*f; } #define lc p<<1 #define rc p<<1|1 inline void pushdown(int p,int l,int r){ int &v=tag[p],mid=(l+r)>>1; if (!T[p].L)T[p].L=++node; if (!T[p].R)T[p].R=++node; tag[T[p].L]+=v,tag[T[p].R]+=v; sz[T[p].L]+=v*(mid-l+1),sz[T[p].R]+=v*(r-mid); v=0; } inline void update(int &p,int ql,int qr,int l=1,int r=n){ if (!p)p=++node; if (ql<=l && r<=qr){ ++tag[p];sz[p]+=r-l+1; return; } if (tag[p])pushdown(p,l,r); rint mid=(l+r)>>1; if (ql<=mid) update(T[p].L,ql,qr,l,mid); if (mid<qr) update(T[p].R,ql,qr,mid+1,r); sz[p]=sz[T[p].L]+sz[T[p].R]; } inline ll getsum(int &p,int ql,int qr,int l=1,int r=n){ if (!p)return 0; if (ql<=l && r<=qr) return sz[p]; if (tag[p])pushdown(p,l,r); rint mid=(l+r)>>1; ll tt=0; if (ql<=mid) tt+=getsum(T[p].L,ql,qr,l,mid); if (mid<qr) tt+=getsum(T[p].R,ql,qr,mid+1,r); return tt; } inline void add(int ql,int qr,int k,int p=1,int l=1,int r=totn){ update(rt[p],ql,qr); if (l==r) return; rint mid=(l+r)>>1; if (k<=mid) add(ql,qr,k,lc,l,mid); else add(ql,qr,k,rc,mid+1,r); } inline int query(int ql,int qr,ll k,int p=1,int l=1,int r=totn){ if (l==r) return b[l]; rint mid=(l+r)>>1; ll tt=getsum(rt[rc],ql,qr); if (tt<k) return query(ql,qr,k-tt,lc,l,mid); else return query(ql,qr,k,rc,mid+1,r); } int main(){ n=read(),m=read(); for (rint i=1;i<=m;++i){ op[i]=read(),ql[i]=read(),qr[i]=read(),k[i]=read(); if (op[i]==1)b[++totn]=k[i]; } sort(b+1,b+totn+1); totn=unique(b+1,b+totn+1)-b-1; for (rint i=1;i<=m;++i) if (op[i]==1)k[i]=lower_bound(b+1,b+totn+1,k[i])-b; for (rint i=1;i<=m;++i){ if (op[i]==1){ add(ql[i],qr[i],k[i]); }else{ printf("%d\n",query(ql[i],qr[i],k[i])); } } return 0; }3.整体二分
这应该属于练整体二分的一道比较基础的题目了。
何谓整体二分?就是直接一起二分所有的询问操作的答案,然后暴力扫描当前操作区间,将其划分为答案的左子区间与右子区间两个部分。
那么以什么为划分依据呢?看看这个操作对于左子区间有没有贡献。如果没有,那么就划分到右子区间中,然后将这个操作的权值更改为这个贡献减去所需的贡献,反之,则划分到左子区间中,同时将这个操作的贡献加入某一个容器,为询问操作服务。
这么说可能有点晕。就这道题说的话,应该是这样:
我们设尚未解决的操作区间为,答案区间为,令当前答案为。
则若该操作是添加操作,如果其添加的,这此次操作对于左子区间有贡献,加入左子区间中,并将区间线段树中的区间整体加.
反之,则将操作加入到右子区间中。
若该操作是询问操作,如果当前的在线段树中查询到的,比它大的数的个数,则证明该询问操作应该在右子区间内可以找到答案。反之,则将,减去此次查询的贡献,然后将询问操作添加到左子区间中。
参考代码:
#include<cstdio> #include<algorithm> #include<cstring> #define rint register int using namespace std; typedef long long ll; const int N=5e4+5; struct Ask{ int op,l,r,id; ll v; }q[N],tl[N],tr[N]; int tag[N<<2],rec[N<<2],ans[N]; ll sum[N<<2]; int n,m,Q; inline int read(){ int x=0,f=1;char ch=getchar(); while (ch<'0' || ch>'9'){if (ch=='-')f=-1;ch=getchar();} while ('0'<=ch && ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();} return x*f; } #define lc p<<1 #define rc p<<1|1 inline void pushdown(int p,int l,int r){ if (rec[p]){ rec[p]=0; tag[lc]=tag[rc]=sum[lc]=sum[rc]=0; rec[lc]=1,rec[rc]=1; } if (tag[p]){ rint mid=(l+r)>>1; tag[lc]+=tag[p],tag[rc]+=tag[p]; sum[lc]+=tag[p]*(mid-l+1); sum[rc]+=tag[p]*(r-mid); tag[p]=0; } } inline void add(int ql,int qr,int w,int p=1,int l=1,int r=n){ if (ql<=l && r<=qr){ tag[p]+=w;sum[p]+=w*(r-l+1); return; } if (tag[p] || rec[p])pushdown(p,l,r); rint mid=(l+r)>>1; if (ql<=mid) add(ql,qr,w,lc,l,mid); if (mid<qr) add(ql,qr,w,rc,mid+1,r); sum[p]=sum[lc]+sum[rc]; } inline ll query(int ql,int qr,int p=1,int l=1,int r=n){ if (ql<=l && r<=qr)return sum[p]; rint mid=(l+r)>>1; ll tt=0; if (tag[p] || rec[p])pushdown(p,l,r); if (ql<=mid) tt+=query(ql,qr,lc,l,mid); if (mid<qr) tt+=query(ql,qr,rc,mid+1,r); return tt; } inline void solve(int st,int en,int l,int r){ if (l==r){ for (rint i=st;i<=en;++i) if (q[i].op==2) ans[q[i].id]=l; return; } rint mid=(l+r)>>1; bool fl=0,fr=0; rint L=0,R=0; rec[1]=1;tag[1]=sum[1]=0; for (rint i=st;i<=en;++i) if (q[i].op==1){ if (q[i].v>mid){ add(q[i].l,q[i].r,1); tr[++R]=q[i]; }else tl[++L]=q[i]; }else{ ll val=query(q[i].l,q[i].r); if (val<q[i].v){ q[i].v-=val; fl=1; tl[++L]=q[i]; }else{ fr=1; tr[++R]=q[i]; } } for (rint i=1;i<=L;++i) q[st+i-1]=tl[i]; for (rint i=L+1;i<=L+R;++i) q[st+i-1]=tr[i-L]; if (fl) solve(st,st+L-1,l,mid); if (fr) solve(st+L,en,mid+1,r); } int main(){ n=read(),m=read(); for (rint i=1;i<=m;++i){ q[i].op=read(),q[i].l=read(),q[i].r=read(),q[i].v=read(); if (q[i].op==2)q[i].id=++Q; } solve(1,m,-n,n); for (rint i=1;i<=Q;++i) printf("%d\n",ans[i]); return 0; }貌似一比,树套树还好写一点哈?
- 1
信息
- ID
- 2405
- 时间
- 1500ms
- 内存
- 500MiB
- 难度
- 6
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者