有N个位置,M个操作。操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c
如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少。
Input
第一行N,M
接下来M行,每行形如1 a b c或2 a b c
Output
输出每个询问的结果
Sample Input
2 5 1 1 2 1 1 1 2 2 2 1 1 2 2 1 1 1 2 1 2 3
Sample Output
1 2 1
Hint
【样例说明】
第一个操作 后位置 1 的数只有 1 , 位置 2 的数也只有 1 。 第二个操作 后位置 1
的数有 1 、 2 ,位置 2 的数也有 1 、 2 。 第三次询问 位置 1 到位置 1 第 2 大的数 是
1 。 第四次询问 位置 1 到位置 1 第 1 大的数是 2 。 第五次询问 位置 1 到位置 2 第 3
大的数是 1 。
N,M<=50000,N,M<=50000
a<=b<=N
1操作中abs(c)<=N
2操作中c<=Maxlongint
解题思路: 树套树的解法就是外层对值域建立线段树,内层对区间建立线段树。
那么对于更细操作就是在外层找到这个值,并且把路径上的区间整体加1。
对于一个查询操作就是类似于主席树查询区间第k大的转移操作。在外层二分找到那个值就可以了。
内层要动态开点。
另外,以前线段树查询修改之类的函数总爱写成递归的形式,在学习的时候发现了一个很好的模板
看这里。
ps:只有内层才支持区间修改(或者可以用标记永久化。)。
树套树在bzoj上过不去。。在洛谷上吸氧还是有一组T掉了。。。。
#include<iostream>
#include<cstdio>
using namespace std;
#define LL long long
#define N 50005
struct node
{
int ls,rs,lazy;
LL sum;
}t[20000000];
int tot;
int T[20000000];
int n;
void push_down(int rt,int len)
{
if(t[rt].lazy)
{
if(!t[rt].ls)t[rt].ls=++tot;
if(!t[rt].rs)t[rt].rs=++tot;
int tmp=t[rt].lazy;
int llen=len-len/2,rlen=len/2;
t[t[rt].ls].sum+=tmp*llen;
t[t[rt].rs].sum+=tmp*rlen;
t[t[rt].ls].lazy+=tmp;
t[t[rt].rs].lazy+=tmp;
t[rt].lazy=0;
}
}
void update(int &rt,int l,int r,int ql,int qr)
{
if(!rt)rt=++tot;
if(l>=ql &&r<=qr)
{
t[rt].sum+=r-l+1;
t[rt].lazy+=1;
return ;
}
push_down(rt,r-l+1);
int m=(l+r)>>1;
if(ql<=m) update(t[rt].ls,l,m,ql,qr);
if(qr>m) update(t[rt].rs,m+1,r,ql,qr);
t[rt].sum=t[t[rt].ls].sum+t[t[rt].rs].sum;
}
void change(int a,int b,int c)
{
int rt=1,l=1,r=n;
while(l!=r)
{
int m=(l+r)>>1;
update(T[rt],1,n,a,b);
if(c<=m)r=m,rt<<=1;
else l=m+1,rt=rt<<1|1;
}
update(T[rt],1,n,a,b);
}
LL query_sub(int rt,int l,int r,int ql,int qr)
{
if(!rt)return 0;
if(l>=ql && r<=qr)
{
return t[rt].sum;
}
push_down(rt,r-l+1);
LL ans=0;
int m=(l+r)>>1;
if(ql<=m) ans+=query_sub(t[rt].ls,l,m,ql,qr);
if(qr>m) ans+=query_sub(t[rt].rs,m+1,r,ql,qr);
return ans;
}
int query(int a,int b,LL c)
{
int rt=1,l=1,r=n;
while(l!=r)
{
int m=(l+r)>>1;
LL ans=query_sub(T[rt<<1],1,n,a,b);
if(c<=ans) r=m,rt<<=1;
else l=m+1,rt=rt<<1|1,c-=ans;
}
return l;
}
int main()
{
int m;
scanf("%d%d",&n,&m);
int op,a,b,c;
while(m--)
{
scanf("%d%d%d",&op,&a,&b);
if(op==1)
{
scanf("%d",&c);
change(a,b,n-c+1);
}
else
{
LL tmp;
scanf("%lld",&tmp);
printf("%d\n",n-query(a,b,tmp)+1);
}
}
}
看率到整体二分的作法:
我们将操作和答案一起二分。
如果当前为修改操作,如果加入的数小于当前的mid的值就对于左区间有贡献(并且统计贡献),否则的话就对右区间有贡献(暂且不统计贡献,直接归于右区间)。
如果当前操作为查询操作,查询当前范围内的值,如果大于它的话归于做区间,否则的话就减掉贡献,归于右区间。
#include<iostream>
#include<cstdio>
using namespace std;
#define LL long long
#define N 50005
#define inf 0x3f3f3f3f
struct node
{
int op,a,b;
LL c;
int id;
}p[N],p1[N],p2[N];
int ou[N];
struct tree
{
LL sum[N*4];
int lazy[N*4];
int n;
void push_down(int rt,int len)
{
if(lazy[rt])
{
int llen=len-len/2;
int rlen=len/2;
sum[rt<<1]+=llen*lazy[rt];
sum[rt<<1|1]+=rlen*lazy[rt];
lazy[rt<<1]+=lazy[rt];
lazy[rt<<1|1]+=lazy[rt];
lazy[rt]=0;
}
}
void update(int rt,int l,int r,int ql,int qr,int val)
{
if(l>=ql && r<=qr)
{
sum[rt]+=(r-l+1)*val;
lazy[rt]+=val;
return ;
}
push_down(rt,r-l+1);
int m=(l+r)>>1;
if(ql<=m) update(rt<<1,l,m,ql,qr,val);
if(qr>m) update(rt<<1|1,m+1,r,ql,qr,val);
sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
LL query(int rt,int l,int r,int ql,int qr)
{
if(l>=ql && r<=qr)
{
return sum[rt];
}
push_down(rt,r-l+1);
LL ans=0;
int m=(l+r)>>1;
if(ql<=m) ans+=query(rt<<1,l,m,ql,qr);
if(qr>m) ans+=query(rt<<1|1,m+1,r,ql,qr);
sum[rt]=sum[rt<<1]+sum[rt<<1|1];
return ans;
}
void add(int l,int r,int v)
{
update(1,1,n,l,r,v);
}
LL ask(int l,int r)
{
return query(1,1,n,l,r);
}
}T;
void solve(int ql,int qr,int l,int r)
{
if(ql>qr)return ;
if(l==r)
{
for(int i=ql;i<=qr;i++){
if(p[i].op==2)ou[p[i].id]=l;
}
return ;
}
int m=(l+r)>>1;
int L=0,R=0;
for(int i=ql;i<=qr;i++)
{
if(p[i].op==1)
{
if(p[i].c<=m)
{
p1[L++]=p[i];
T.add(p[i].a,p[i].b,1);
}
else p2[R++]=p[i];
}
else
{
LL res=T.ask(p[i].a,p[i].b);
if(res>=p[i].c) p1[L++]=p[i];
else
{
p[i].c-=res;
p2[R++]=p[i];
}
}
}
for(int i=0;i<L;i++){
if(p1[i].op==1){
T.add(p1[i].a,p1[i].b,-1);
}
}
int now=ql;
for(int i=0;i<L;i++)p[now++]=p1[i];
for(int i=0;i<R;i++)p[now++]=p2[i];
solve(ql,ql+L-1,l,m);
solve(ql+L,ql+L+R-1,m+1,r);
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
T.n=n;
int op,a,b;
LL c;
int id=0;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d%lld",&op,&a,&b,&c);
if(op==1)c=n-c+1;
if(op==2)id++;
p[i]=node{op,a,b,c,id};
}
solve(1,m,-n,n);
for(int i=1;i<=id;i++)printf("%d\n",n+1-ou[i]);
}
另外,如果把整体二分里边的线段树换为树状数组的话应该会更快。