Description
Jry最近做(屠)了很多数据结构题,所以想 BS你,他希望你能实现一种数据结构维护一个序列:
Input
第一行n;
第二行n个数;
第三行q,代表询问个数;
接下来q行,每行一个op,输入格式见描述。
Output
对于7≤op≤11的操作,一行输出一个答案。
Sample Input
6
5 2 6 3 1 4
15
7 2 4
8 1 3
9 2 4 5
10 1 6 4
11 2 5 4
6 1 4 7
8 1 4
5 3 4 5
2 1
1 2 8
3 3 5
4 1 5 2
9 2 5 4
10 3 6 4
11 1 6 100
Sample Output
11
4
1
4
3
0
3
12
6
HINT
n,q≤100000
任意时刻数列中的数≤2^31-1
0≤任意时刻数列中的数≤2^31-1
本题共3组数据
分析:
- opt=1
块状链表的基本操作:find插入的位置,分裂成两块,单元素插入 - opt=2
块状链表的基本操作:find插入位置,直接在第now块O(n)修改即可 - opt=3
查找到[x,y]区间,分裂出来,设置一个翻转标记,翻转整个区间(每个块打上翻转标记,链表的连接方式也翻过来) - opt=4
查找到[x,x+k-1]和[x+k,y]两个区间,直接变换指针 - opt=5
查找到[x,y]区间,分裂出来,设置一个覆盖标记,每个块都打上覆盖标记 - opt=6
查找到[x,y]区间,分裂出来,设置一个修改标记,每个块都打上修改标记 - opt=7
每个块维护一个sum值,直接询问[x,y]的和 - opt=8
在每个块内维护一个新的数组s,把块内的元素按照从小到大排好序,直接查询最大值和最小值即可 - opt=9
查找到[x,y]区间,分裂出来,在每个块的s数组中二分查找询问元素,记录前驱后继与询问元素的差值最小值 - opt=10
二分答案,转化成判定问题:查找到[x,y]区间,分裂出来,在每个块的s数组中二分查找询问元素,统计比询问元素的小的元素个数 - opt=11
询问10的子问题
块状链表的基本操作还是比较好些好想的,然而这道题的扩展操作比较多,我们来看一看code:
初始化
块内维护信息:
d:原数组
s:从小到大排序后的数组
rev:翻转标记
delta:加标记
same:覆盖标记
size:块内的元素个数
sum:块内元素和
nxt:链指针
struct node{
int d[1003],s[1003];
int rev,delta,same,size,nxt;
ll sum;
};
node a[N];
queue<int> q; //内存池
void clear(int x)
{
a[x].sum=a[x].rev=a[x].delta=a[x].same=a[x].size=0;
a[x].nxt=-1;
}
int newnode() //申请新结点
{
int t=q.front(); q.pop();
return t;
}
int del(int x) //删除结点,恢复到内存池
{
q.push(x); clear(x);
}
int init() //初始化
{
for (int i=1;i<N;i++) q.push(i);
a[0].nxt=-1; a[0].size=0;
}
定位
void find(int &pos,int &now)
{
for (now=0;a[now].nxt!=-1&&pos>a[now].size;now=a[now].nxt) pos-=a[now].size;
}
合并
合并的思路还是从now开始一路遍历下去
如果相邻块的大小之和小于blocksize(sqrt(n)),我们就进行合并
在合并之前,我们要保证块内的信息是稳定的,因此我们要标记下放
合并完成之后,每一块的信息就有变化了
我们就要重新维护块的信息
void merge(int now) //now+now.nxt ---> now
{
int t=a[now].nxt;
push(now); push(t);
for (int i=1;i<=a[t].size;i++)
a[now].d[++a[now].size]=a[t].d[i];
a[now].nxt=a[t].nxt;
del(t);
update(now);
}
void maintain(int now) //合并小块
{
for (;now!=-1;now=a[now].nxt)
if (a[now].nxt!=-1&&a[now].size+a[a[now].nxt].size<=blocksize)
merge(now);
}
维护信息
信息的维护分成两种:
- 维护s数组和sum:我们直接把d数组复制到s数组中,sort一下即可
- 标记下放:块状链表也不是什么线段树,每个块的大小只有sqrt(n)左右,所以直接O(n)处理即可
void push(int now)
{
if (a[now].rev)
{
a[now].rev=0;
for (int i=1;i<=a[now].size/2;i++)
swap(a[now].d[i],a[now].d[a[now].size-i+1]);
}
if (a[now].same)
{
for (int i=1;i<=a[now].size;i++)
a[now].d[i]=a[now].same;
a[now].sum=a[now].size*a[now].same;
a[now].same=0;
}
if (a[now].delta)
{
for (int i=1;i<=a[now].size;i++)
a[now].d[i]+=a[now].delta;
a[now].sum+=a[now].size*a[now].delta;
a[now].delta=0;
}
}
void update(int x)
{
a[x].sum=0;
for (int i=1;i<=a[x].size;i++)
a[x].s[i]=a[x].d[i],
a[x].sum+=a[x].d[i];
sort(a[x].s+1,a[x].s+1+a[x].size); //维护顺序
}
分裂
申请新结点,把后半部分直接复制到新结点中,不要忘了维护指针
要注意:分裂点我们划分到了下一块
这个性质关乎之后的操作
void divide(int now,int pos) //分裂 pos分到了下一块
{
push(now);
int t=newnode();
for (int i=pos;i<=a[now].size;i++)
a[t].d[++a[t].size]=a[now].d[i];
a[t].nxt=a[now].nxt; a[now].nxt=t;
a[now].size=max(pos-1,0);
update(t);
update(now);
}
单点插入
我们先要定位插入的位置:第now块的第pos位
之后把第now块分裂开
因为在分裂的时候pos分裂到了后一块,所以一开始的时候要pos++
因为块的大小和信息都有变化,所以我们调用一下maintain(合并小块)
void insert(int pos,int val)
{
int now; pos++;
find(pos,now); divide(now,pos);
a[now].d[++a[now].size]=val;
update(now);
maintain(now);
}
单点删除
因为是单点删除,我们不用分裂,直接把后面的元素前移即可
因为块的大小和信息都有变化,所以我们调用一下maintain(合并小块)
void erase(int pos)
{
int now;
find(pos,now); push(now);
for (int i=pos;i<a[now].size;i++)
a[now].d[i]=a[now].d[i+1];
a[now].size--;
update(now);
maintain(now);
}
分裂区间
这个函数非常的重要
调用后返回的lp和rp就是:
因为块的大小和信息都有变化,所以我们调用一下maintain(合并小块)
void solve(int l,int r,int &lp,int &rp)
{
int pos=l;
find(pos,lp); divide(lp,pos);
pos=r+1; find(pos,rp); divide(rp,pos);
pos=r; find(pos,rp);
}
区间翻转
我们需要一个辅助数组,把区间的块信息记录下来
方便翻转区间链接方式
因为块的大小和信息都有变化,所以我们调用一下maintain(合并小块)
void reverse(int x,int y)
{
int lp,rp;
solve(x,y,lp,rp);
int now=lp,top=0;
for (int i=a[lp].nxt;i!=a[rp].nxt;i=a[i].nxt)
st[++top]=i,a[i].rev^=1;
a[st[1]].nxt=a[rp].nxt; //整个区间翻转
for (int i=top;i>1;i--)
a[st[i]].nxt=st[i-1];
a[lp].nxt=rp;
maintain(lp);
}
区间右移
唯一需要注意的就是指针的修改
因为块的大小和信息都有变化,所以我们调用一下maintain(合并小块)
void move(int x,int y,int z)
{
int lp,rp,mp,np;
solve(x,y-z,lp,mp);
solve(y-z+1,y,mp,rp);
np=a[lp].nxt;
a[lp].nxt=a[mp].nxt;
a[mp].nxt=a[rp].nxt;
a[rp].nxt=np;
maintain(lp);
}
区间修改+询问区间和
这个操作比较简单
注意我们在修改的时候,直接维护了sum值
因为块的大小和信息都有变化,所以我们调用一下maintain(合并小块)
void add(int x,int y,int z)
{
int lp,rp;
solve(x,y,lp,rp);
for (int i=a[lp].nxt;i!=a[rp].nxt;i=a[i].nxt)
{
a[i].delta+=z;
a[i].sum+=a[i].size*z;
}
maintain(lp);
}
void same(int x,int y,int z)
{
int lp,rp;
solve(x,y,lp,rp);
for (int i=a[lp].nxt;i!=a[rp].nxt;i=a[i].nxt)
{
a[i].delta=0;
a[i].same=z;
a[i].sum=a[i].size*z;
}
maintain(lp);
}
ll sum(int x,int y)
{
int lp,rp;
solve(x,y,lp,rp);
ll ans=0;
for (int i=a[lp].nxt;i!=a[rp].nxt;i=a[i].nxt)
ans+=a[i].sum;
maintain(lp);
return ans;
}
询问区间最值
在每个结点中,我们维护了一个s数组,所以我们可以直接从s数组中查找
但是有一个问题:我们在调用这个块的时候,块的信息可能没有push过
如果我们每访问一个块就push一次,O(n)的复杂度不太划算
因为修改操作只有加和区间覆盖,是不会影响元素之间的大小关系的
所以我们直接把delta累加到元素上即可
(有覆盖标记的块需要特判一下)
因为我们调用了分裂操作,块的大小有变化,所以我们调用一下maintain(合并小块)
int range(int x,int y)
{
int lp,rp;
solve(x,y,lp,rp);
int mn=INF,mx=-INF;
for (int i=a[lp].nxt;i!=a[rp].nxt;i=a[i].nxt) if (a[i].size)
{
if (a[i].same)
{
mn=min(mn,a[i].same+a[i].delta); //可能没有push
mx=max(mx,a[i].same+a[i].delta);
}
else
{
mn=min(mn,a[i].s[1]+a[i].delta);
mx=max(mx,a[i].s[a[i].size]+a[i].delta);
}
}
maintain(lp);
return mx-mn;
}
询问最小差值
在每个结点中,我们维护了一个s数组
我们可以直接在s数组中调用lower_bound函数,找到大于等于询问元素的第一个位置
注意:我们在调用这个块的时候,块的信息可能没有push过
我们要把delta累加到元素上
(有覆盖标记的块需要特判一下)
因为我们调用了分裂操作,块的大小有变化,所以我们调用一下maintain(合并小块)
int near(int x,int y,int z)
{
int lp,rp;
solve(x,y,lp,rp);
int ans=INF;
for (int i=a[lp].nxt;i!=a[rp].nxt;i=a[i].nxt)
{
if (a[i].same) ans=min(ans,abs(a[i].same+a[i].delta-z));
else
{
int x=lower_bound(a[i].s+1,a[i].s+a[i].size+1,z-a[i].delta)-a[i].s;
if (x!=a[i].size+1)
ans=min(ans,abs(a[i].s[x]+a[i].delta-z));
if (x!=1)
x--,ans=min(ans,abs(a[i].s[x]+a[i].delta-z));
}
}
maintain(lp);
return ans;
}
询问第K值
我们二分一个答案M
统计比M小的元素个数,计算出M的排名
在s数组中调用upper_bound函数,找到大于询问元素的第一个位置x
那么x-1就是小于M的元素个数
还是这个问题:我们在调用这个块的时候,块的信息可能没有push过
我们要把delta累加到元素上
(有覆盖标记的块需要特判一下)
因为我们调用了分裂操作,块的大小有变化,所以我们调用一下maintain(合并小块)
int min_k(int x,int y,int z)
{
int lp,rp;
solve(x,y,lp,rp);
int L=0,R=INF,ans;
while (L<R)
{
int M=((L+R)>>1)+1;
int sum=1; //排名
for (int i=a[lp].nxt;i!=a[rp].nxt;i=a[i].nxt)
{
if (a[i].same)
{
if (a[i].same+a[i].delta<M) sum+=a[i].size;
}
else
{
int x=upper_bound(a[i].s+1,a[i].s+a[i].size+1,M-a[i].delta-1)-a[i].s;
sum+=max(x-1,0);
}
}
if (sum<=z) L=M;
else R=M-1;
}
maintain(lp);
return L;
}
询问小于某元素的元素个数
上一种询问的子问题
int ask_xiao(int x,int y,int z)
{
int lp,rp;
solve(x,y,lp,rp);
int ans=0;
for (int i=a[lp].nxt;i!=a[rp].nxt;i=a[i].nxt)
{
if (a[i].same)
{
if (a[i].same+a[i].delta<z) ans+=a[i].size;
}
else
{
int x=upper_bound(a[i].s+1,a[i].s+a[i].size+1,z-a[i].delta-1)-a[i].s;
ans+=max(x-1,0);
}
}
maintain(lp);
return ans;
}
tip
一开始TLE了
发现是move的时候maintain传入的起点错了
这样就会导致小的块没有及时合并,时间退化的比较厉害
lower_bound和upper_bound的应用要注意
#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
#include<algorithm>
#include<cmath>
#define ll long long
using namespace std;
const int INF=1e9;
const int N=10000;
int n,m,blocksize,st[N],b[N];
struct node{
int d[1003],s[1003];
int rev,delta,same,size,nxt;
ll sum;
};
node a[N];
queue<int> q;
void clear(int x)
{
a[x].sum=a[x].rev=a[x].delta=a[x].same=a[x].size=0;
a[x].nxt=-1;
}
int newnode()
{
int t=q.front(); q.pop();
return t;
}
int del(int x)
{
q.push(x); clear(x);
}
int init()
{
for (int i=1;i<N;i++) q.push(i);
a[0].nxt=-1; a[0].size=0;
}
void find(int &pos,int &now)
{
for (now=0;a[now].nxt!=-1&&pos>a[now].size;now=a[now].nxt) pos-=a[now].size;
}
void push(int now)
{
if (a[now].rev)
{
a[now].rev=0;
for (int i=1;i<=a[now].size/2;i++)
swap(a[now].d[i],a[now].d[a[now].size-i+1]);
}
if (a[now].same)
{
for (int i=1;i<=a[now].size;i++)
a[now].d[i]=a[now].same;
a[now].sum=a[now].size*a[now].same;
a[now].same=0;
}
if (a[now].delta)
{
for (int i=1;i<=a[now].size;i++)
a[now].d[i]+=a[now].delta;
a[now].sum+=a[now].size*a[now].delta;
a[now].delta=0;
}
}
void update(int x)
{
a[x].sum=0;
for (int i=1;i<=a[x].size;i++)
a[x].s[i]=a[x].d[i],
a[x].sum+=a[x].d[i];
sort(a[x].s+1,a[x].s+1+a[x].size); //维护顺序
}
void merge(int now) //now+now.nxt ---> now
{
int t=a[now].nxt;
push(now); push(t);
for (int i=1;i<=a[t].size;i++)
a[now].d[++a[now].size]=a[t].d[i];
a[now].nxt=a[t].nxt;
del(t);
update(now);
}
void maintain(int now) //合并小块
{
for (;now!=-1;now=a[now].nxt)
if (a[now].nxt!=-1&&a[now].size+a[a[now].nxt].size<=blocksize)
merge(now);
}
void divide(int now,int pos) //分裂 pos分到了下一块
{
push(now);
int t=newnode();
for (int i=pos;i<=a[now].size;i++)
a[t].d[++a[t].size]=a[now].d[i];
a[t].nxt=a[now].nxt; a[now].nxt=t;
a[now].size=max(pos-1,0);
update(t);
update(now);
}
void insert(int pos,int val)
{
int now; pos++;
find(pos,now); divide(now,pos);
a[now].d[++a[now].size]=val;
update(now);
maintain(now);
}
void erase(int pos)
{
int now;
find(pos,now); push(now);
for (int i=pos;i<a[now].size;i++)
a[now].d[i]=a[now].d[i+1];
a[now].size--;
update(now);
maintain(now);
}
void solve(int l,int r,int &lp,int &rp)
{
int pos=l;
find(pos,lp); divide(lp,pos);
pos=r+1; find(pos,rp); divide(rp,pos);
pos=r; find(pos,rp);
}
void reverse(int x,int y)
{
int lp,rp;
solve(x,y,lp,rp);
int now=lp,top=0;
for (int i=a[lp].nxt;i!=a[rp].nxt;i=a[i].nxt)
st[++top]=i,a[i].rev^=1;
a[st[1]].nxt=a[rp].nxt; //整个区间翻转
for (int i=top;i>1;i--)
a[st[i]].nxt=st[i-1];
a[lp].nxt=rp;
maintain(lp);
}
void move(int x,int y,int z)
{
int lp,rp,mp,np;
solve(x,y-z,lp,mp);
solve(y-z+1,y,mp,rp);
np=a[lp].nxt;
a[lp].nxt=a[mp].nxt;
a[mp].nxt=a[rp].nxt;
a[rp].nxt=np;
maintain(lp);
}
void add(int x,int y,int z)
{
int lp,rp;
solve(x,y,lp,rp);
for (int i=a[lp].nxt;i!=a[rp].nxt;i=a[i].nxt)
{
a[i].delta+=z;
a[i].sum+=a[i].size*z;
}
maintain(lp);
}
void same(int x,int y,int z)
{
int lp,rp;
solve(x,y,lp,rp);
for (int i=a[lp].nxt;i!=a[rp].nxt;i=a[i].nxt)
{
a[i].delta=0;
a[i].same=z;
a[i].sum=a[i].size*z;
}
maintain(lp);
}
ll sum(int x,int y)
{
int lp,rp;
solve(x,y,lp,rp);
ll ans=0;
for (int i=a[lp].nxt;i!=a[rp].nxt;i=a[i].nxt)
ans+=a[i].sum;
maintain(lp);
return ans;
}
int range(int x,int y)
{
int lp,rp;
solve(x,y,lp,rp);
int mn=INF,mx=-INF;
for (int i=a[lp].nxt;i!=a[rp].nxt;i=a[i].nxt) if (a[i].size)
{
if (a[i].same)
{
mn=min(mn,a[i].same+a[i].delta); //可能没有push
mx=max(mx,a[i].same+a[i].delta);
}
else
{
mn=min(mn,a[i].s[1]+a[i].delta);
mx=max(mx,a[i].s[a[i].size]+a[i].delta);
}
}
maintain(lp);
return mx-mn;
}
int near(int x,int y,int z)
{
int lp,rp;
solve(x,y,lp,rp);
int ans=INF;
for (int i=a[lp].nxt;i!=a[rp].nxt;i=a[i].nxt)
{
if (a[i].same) ans=min(ans,abs(a[i].same+a[i].delta-z));
else
{
int x=lower_bound(a[i].s+1,a[i].s+a[i].size+1,z-a[i].delta)-a[i].s;
if (x!=a[i].size+1)
ans=min(ans,abs(a[i].s[x]+a[i].delta-z));
if (x!=1)
x--,ans=min(ans,abs(a[i].s[x]+a[i].delta-z));
}
}
maintain(lp);
return ans;
}
int min_k(int x,int y,int z)
{
int lp,rp;
solve(x,y,lp,rp);
int L=0,R=INF,ans;
while (L<R)
{
int M=((L+R)>>1)+1;
int sum=1; //排名
for (int i=a[lp].nxt;i!=a[rp].nxt;i=a[i].nxt)
{
if (a[i].same)
{
if (a[i].same+a[i].delta<M) sum+=a[i].size;
}
else
{
int x=upper_bound(a[i].s+1,a[i].s+a[i].size+1,M-a[i].delta-1)-a[i].s;
sum+=max(x-1,0);
}
}
if (sum<=z) L=M;
else R=M-1;
}
maintain(lp);
return L;
}
int ask_xiao(int x,int y,int z)
{
int lp,rp;
solve(x,y,lp,rp);
int ans=0;
for (int i=a[lp].nxt;i!=a[rp].nxt;i=a[i].nxt)
{
if (a[i].same)
{
if (a[i].same+a[i].delta<z) ans+=a[i].size;
}
else
{
int x=upper_bound(a[i].s+1,a[i].s+a[i].size+1,z-a[i].delta-1)-a[i].s;
ans+=max(x-1,0);
}
}
maintain(lp);
return ans;
}
int main()
{
scanf("%d",&n);
int opt,x,y,z;
blocksize=sqrt(n);
init();
for (int i=1;i<=n;i++)
{
scanf("%d",&x);
insert(i-1,x); //从0开始
}
scanf("%d",&m);
for (int i=1;i<=m;i++)
{
scanf("%d",&opt);
switch (opt)
{
case 1:scanf("%d%d",&x,&y); insert(x,y); break;
case 2:scanf("%d",&x); erase(x); break;
case 3:scanf("%d%d",&x,&y); reverse(x,y); break;
case 4:scanf("%d%d%d",&x,&y,&z); move(x,y,z); break;
case 5:scanf("%d%d%d",&x,&y,&z); add(x,y,z); break;
case 6:scanf("%d%d%d",&x,&y,&z); same(x,y,z); break;
case 7:scanf("%d%d",&x,&y); printf("%lld\n",sum(x,y)); break;
case 8:scanf("%d%d",&x,&y); printf("%d\n",range(x,y)); break;
case 9:scanf("%d%d%d",&x,&y,&z); printf("%d\n",near(x,y,z)); break;
case 10:scanf("%d%d%d",&x,&y,&z); printf("%d\n",min_k(x,y,z)); break;
case 11:scanf("%d%d%d",&x,&y,&z); printf("%d\n",ask_xiao(x,y,z)); break;
}
}
return 0;
}