题目大意:在给定的区间里面找出sort后能分成连续的几段,
莫队算法:利用[L,R]区间的值去算出[L,R+1],[L,R-1],[L-1,R],[L+1,R]四个区间的值,适用于离线处理区间的问题,一般先把询问排序,顺序看题目要求,一般要分块处理,为了降低复杂度,让每次指针的移动次数都尽可能的少,然后暴力求出第一组解,再依次往下推。
针对本题,求出了[l,r]区间之后去推另一个区间的值,加入另一个区间是[l,r+1],就把r+1这个位置处理(update),原数组为a,把a[i+1]处理,利用vis记录当前区间里有没有那个数字,如果既有a[i+1]-1,又有a[i+1]+1,那么就相当于a[i+1]把这两段给连了起来,总的值减一,其他的也可以这么推出来。
AC代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define maxn 100010
using namespace std;
struct que{
int l;
int r;
int pos;
}qu[maxn];
int a[maxn],pos[maxn],ans,n,m,cnt[maxn]; //a记录原始数据,pos记录每个数字出现的位置,以便于分块,cnt用来记录最后的结果
bool vis[maxn];
int cmp(que a,que b)
{
if(pos[a.l] == pos[b.l]) //如果左端点在同一个区块里面,按右端点从小到大排列
return a.r<b.r;
return pos[a.l] < pos[b.l]; //否则按左端点区块递增排列
}
void update(int x,int k) //往询问的区间里添加或者删减元素,k==1是加,k==0是减
{
vis[x] = k;
if(k)
ans += 1-vis[x-1] - vis[x+1]; //如果x前面的和后面的数字都已经在区间里面,那么x把两个区间合成了一个,总体减一
else
ans += vis[x-1] + vis[x+1] - 1;//如果x-1与x+1在区间里面,x就把整个区间分隔开了,整体加一
}
int main(){
int t;
scanf("%d",&t);
while(t--)
{
memset(vis,0,sizeof(vis));
scanf("%d%d",&n,&m);
int block = ceil(sqrt(n));
for(int i = 1; i <= n; i++)
{
scanf("%d",&a[i]);
pos[i] = (i-1)/block; //pos记录i这个位置在哪个区块
}
for(int i = 0; i < m; i++)
{
scanf("%d%d",&qu[i].l,&qu[i].r);
qu[i].pos = i;
}
sort(qu,qu+m,cmp);
ans = 0;
int ll,rr;
rr = 0;
ll = 1;
for(int i = 0; i < m; i++)
{
int tem = qu[i].pos;
if(rr<qu[i].r)
for(int j = rr+1; j <= qu[i].r; j++)
update(a[j],1);
if(rr > qu[i].r)
for(int j = rr; j > qu[i].r; j--)
update(a[j],0);
if(ll < qu[i].l)
for(int j = ll; j < qu[i].l; j++)
update(a[j],0);
if(ll > qu[i].l)
for(int j = ll-1; j >= qu[i].l; j--)
update(a[j],1);
ll = qu[i].l;
rr = qu[i].r;
cnt[tem] = ans;
}
for(int i = 0; i < m; i++)
printf("%d\n",cnt[i]);
}
}