您的位置:首页 > 其它

BZOJ3110[Zjoi2013]K大数查询(树状数组+整体二分)

2018-11-11 02:44 621 查看
3110 [Zjoi2013]K大数查询

有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

 题解:

题意概括

  有N个位置,M个操作。操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c。如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少。

N,M<=50000
a<=b<=N
1操作中abs(c)<=N
2操作中c<=Maxlongint

题解

  让我们来考虑神奇的分治算法。

  整体二分!!(当你会了)

  首先当你已经掌握了树状数组的区间加和区间询问(如果不会->点这里

  我们考虑二分答案。

  注意进行以下操作要严格按照输入时间先后顺序来。

  首先对于加进去的数字c,我们把他变成n-c+1,这样就把询问前k大变成了前k小。

  如果是修改操作,如果修改的值比当前的mid值小,就修改,并扔到左区间里面。否则扔到右边。

  如果是询问操作,如果在当前的状态下,该询问的区间内查询到的数的个数res比当前询问的c要大(或者相等),那么显然答案在左区间,把他扔到左边,否则把他的c减掉res再扔到右边去。

  然后递归分治两个区间就可以了。

  (本质是个二分答案的升级版)

  

参考代码:

/**************************************************************
Problem: 3110
User: SongHL
Language: C++
Result: Accepted
Time:1880 ms
Memory:3640 kb
****************************************************************/

#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&-x
#define clr(a,b) memset(a,b,sizeof a)
typedef long long ll;
const int maxn=50005;
int n,m,id[maxn],templ[maxn],tempr[maxn];
ll tree[2][maxn];
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9') {x=x*10+ch-'0'; ch=getchar();}
return x*f;
}
inline void add(int t,int x,int y)//单点加
{
while(x<=n+1)
{
tree[t][x]+=y;
x+=lowbit(x);
}
}
inline void update(int l,int r,int val)//区间加
{
add(0,l,val);add(1,l,l*val);
add(0,r+1,-val);add(1,r+1,-val*(r+1));
}
inline ll Sum(int t,int x)//前缀和
{
ll ans=0;
while(x>0)
{
ans+=tree[t][x];
x-=lowbit(x);
}
return ans;
}
inline ll query(int l,int r)//区间求和
{
return Sum(0,r)*(r+1)-Sum(0,l)*l-Sum(1,r)+Sum(1,l);
}
struct Node{
int type,l,r,x,ans;
void Get()
{
type=read();l=read();r=read();x=read();
if(type==1) x=n-x+1;
}
} a[maxn];

inline void Solve(int lx,int rx,int l,int r)
{
if(l>r) return ;
if(lx==rx)
{
for(int i=l;i<=r;++i) a[id[i]].ans=lx;
return ;
}
int midx=lx+rx>>1;
int L=0,R=0;
for(int i=l;i<=r;++i)
{
if(a[id[i]].type==1)
{
if(a[id[i]].x<=midx) templ[++L]=id[i],update(a[id[i]].l,a[id[i]].r,1);
else tempr[++R]=id[i];
}
else
{
ll res=query(a[id[i]].l,a[id[i]].r);
if(res>=a[id[i]].x) templ[++L]=id[i];
else tempr[++R]=id[i],a[id[i]].x-=res;
}
}
for(int i=1;i<=L;++i)
{
if(a[templ[i]].type==1)
update(a[templ[i]].l,a[templ[i]].r,-1);
}

for(int i=l;i<=l+L-1;++i) id[i]=templ[i-(l-1)];
for(int i=r-R+1;i<=r;++i) id[i]=tempr[i-(r-R)];

Solve(lx,midx,l,l+L-1);
Solve(midx+1,rx,r-R+1,r);
}

int main()
{
n=read();m=read();
for(int i=1;i<=m;++i) a[i].Get(),id[i]=i;
clr(tree,0);
Solve(1,2*n+1,1,m);
for(int i=1;i<=m;++i)
if(a[i].type==2) printf("%d\n",n-a[i].ans+1);
return 0;
}

  

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: