题目传送门
题目大意:给你一个序列无序排布(没有重复数字),进行询问,给出l和r,问在[l,r]区间中,连续的段有多少个。比如234176这个数列中有两个段,分别为2341和76,一个段可以排成连续的一串数字。
思路:这个题用到树状数组来进行单点更新和区间值的修改。此外,还用到了离线处理。将所有的查询按照左点进行分类然后将整个序列从右往左计算,或者将所有的查询按照右端点进行分类然后将整个序列从左往右计算。那么问题来了,为什么需要这么写呢?以从后往前遍历为例,我们在这里用到一个贡献值的思想,先假设每个点都是单独的一个段,那么对于他后面的所有区间都会受到这个单独贡献的影响。那么当我们处理当前的a[i]的时候发现a[i]-1或a[i]+1之前处理过,找到a[i]-1和a[i]+1的位置,用树状数组更新消除对后面的影响。然后看询问里是否有以当前点为左端点的询问,将结果保存,因为后面的都被处理过了,数列再往前的更新不会影响到后面的值(这里我想了一下,脑子短路想了很久,就是原本想到再往前更新的值可能会对后面区间的产生影响,但其实不会,因为计算好了的区间值如果要发生改变只能是前面的值对后面有影响,但是你的区间开在了后面,所以根本就不影响,反而考虑了就错了。一开始理解的不够透彻)。
code
#include<bits/stdc++.h>
using namespace std;
const int maxx = 1e5+100;
vector<pair<int,int> > v[maxx];
int c[maxx];
int a[maxx];
int p[maxx];
int ans[maxx];
int n,m;
int lowbit(int x)
{
return x&(-x);
}
void update(int x,int d)
{
for(int i=x;i<=n;i+=lowbit(i)){
c[i]+=d;
}
}
int getsum(int x)
{
int ans=0;
for(int i=x;i>0;i-=lowbit(i)){
ans+=c[i];
}
return ans;
}
int main ()
{
int t;
int x,y;
scanf("%d",&t);
while(t--){
memset(p,-1,sizeof(p));
memset(a,0,sizeof(a));
memset(c,0,sizeof(c));
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i=0;i<=n;i++)
v[i].clear();
for(int i=0;i<m;i++){
scanf("%d%d",&x,&y);
v[x].push_back(make_pair(y,i));
}
for(int i=n;i>0;i--){
update(i,1); //先计算贡献
p[a[i]] = i; //某个值出现的位置
int ll = p[a[i]-1];
int rr = p[a[i]+1];
//看改数字相邻的数字是否之前处理过
if(ll!=-1) update(p[a[i]-1],-1);
if(rr!=-1) update(p[a[i]+1],-1);
for(int j=0;j<v[i].size();j++){
int index = v[i][j].second;
int r = v[i][j].first;
ans[index] = getsum(r) - getsum(i-1);
}
}
for(int i=0;i<m;i++){
printf("%d\n",ans[i]);
}
}
return 0;
}
自身水平有限,还需要多练多想。