原题:Group
题意:
一个数组,给多个区间,每个位置有个不会重复的编号,编号连续的数可以组队,k个数的队价值为k*k,每个询问给一个区间,求区间的最大价值需要多少个组
解析:
2*2+1*1肯定是3*3大,所以相当于问在排序后有多少片连续的数
对于这种分析数出现不出现,出现几次的问题,且多个询问可以离线的情况,可以用莫队来做
首先,离线处理后,进行这个算法最重要的询问排序
对于询问(l,r),我们对左界分块,同分块,k=sqrt(n)最快,而对于l属于相同块的询问,我们按照r进行升序排列
这样排序的好处是
- 如果当前询问与下一个询问同块,左指针最多移动sqrt(n)的距离
当前块内有多少询问,当前块内的右指针只会向右走,所以这个块的右指针至多走n次,而左指针至多走q*sqrt(n)次
一个块内的复杂度最多为 n+q*sqrt(n).
扫描二维码关注公众号,回复: 2479140 查看本文章
在排序好后,我们用两个指针分别指向当前询问的左界和右界,在跳到下一个询问的时候只需要移动两个指针就好(类似尺取法),对加进来的数,have[]++,删去的数- -,就可以维护当前区间的数了
而加进来的数可以连接当前的两条(2,4,5加进3)就可以使条数- -,连一边则不变(2,3加进4),一边都没有就++。注意一下点1,因为没有点0
代码:
//写的有点冗长了
D read(){ D ans=0; char last=' ',ch=getchar();
while(ch<'0' || ch>'9')last=ch,ch=getchar();
while(ch>='0' && ch<='9')ans=ans*10+ch-'0',ch=getchar();
if(last=='-')ans=-ans; return ans;
}
int a[100009];
int k;
int idx(int a){
return (a-1)/k+1;
}
int have[100009];
struct node{
int l,r,_id;
bool operator <(const node a)const{
if(idx(l)!=idx(a.l)){
return l<a.l;
}
return r<a.r;
}
}e[100009];
int ans[100009];
int main(){
int t=read();while(t--){
mmm(have,0);
int n=read(),q=read();
k=int(sqrt(n));
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1;i<=q;i++)e[i]._id=i,e[i].l=read(),e[i].r=read();
sort(e+1,e+1+q);
int l=0,r=0;
int nowans=0;
for(int i=1;i<=q;i++){
while(r<e[i].r){
r++;have[a[r]]++;
if(a[r]==0);
else if(a[r]==1){
if(have[2]<=0)nowans++;
}
else if(have[a[r]-1]&&have[a[r]+1])nowans--;
else if(have[a[r]-1]<=0&&have[a[r]+1]<=0)nowans++;
}
while(l<e[i].l){
have[a[l]]--;
if(a[l]==0);
else if(a[l]==1){
if(have[2]<=0)nowans--;
}
else if(have[a[l]-1]&&have[a[l]+1])nowans++;
else if(have[a[l]-1]<=0&&have[a[l]+1]<=0)nowans--;
l++;
}
while(r>e[i].r){
have[a[r]]--;
if(a[r]==0);
else if(a[r]==1){
if(have[2]<=0)nowans--;
}
else if(have[a[r]-1]&&have[a[r]+1])nowans++;
else if(have[a[r]-1]<=0&&have[a[r]+1]<=0)nowans--;
r--;
}
while(l>e[i].l){
l--;have[a[l]]++;
if(a[l]==0);
else if(a[l]==1){
if(have[2]<=0)nowans++;
}
else if(have[a[l]-1]&&have[a[l]+1])nowans--;
else if(have[a[l]-1]<=0&&have[a[l]+1]<=0)nowans++;
}
ans[e[i]._id]=nowans;
}
for(int i=1;i<=q;i++){
printf("%d\n",ans[i]);
}
}
}
重新整理了一下莫队,再发一题
题意:
每次询问求区间l,r内所有不同的数的总和
当然了,这次除了莫队以外还需要离散化才能过。
对于每个A[i],先map得到这个数的标号,然后对于原数组的每个位置都放那个位置上数的标号,这样预处理后就可以用数组代替map存出现次数了
代码:
int a[30009];
int k;
int qid(int x){return (x-1)/k+1;}
struct node{
int l,r,idx;
bool operator < (const node a){
if(qid(l)==qid(a.l))return r<a.r;
return l<a.l;
}
}q[100009];
map<int,int>tmp;
int id[30009];//id[3]==4:位置3放的是标号为4的数
int have[30009];
D sum;
D ans[100009];
void del(int p){int x=id[p];have[x]--;if(!have[x])sum-=(D)a[p];}
void add(int p){int x=id[p];if(!have[x])sum+=(D)a[p];have[x]++;}
int main(){
int t=read();
while(t--){
mmm(have,0);sum=0;tmp.clear();
int n=read();k=(int)sqrt(n);
int pos=0;
for1(i,1,n){
a[i]=read();
if(tmp.find(a[i])==tmp.end())id[i]=++pos,tmp[a[i]]=pos;
else id[i]=tmp[a[i]];
}
int que=read();for1(i,1,que)q[i].l=read(),q[i].r=read(),q[i].idx=i;
sort(q+1,q+1+que);
int l=0,r=0;
for1(i,1,que){
while(q[i].r>r)add(++r);
while(q[i].l<l)add(--l);
while(q[i].r<r)del(r--);
while(q[i].l>l)del(l++);
ans[q[i].idx]=sum;
}
for1(i,1,que)printf("%lld\n",ans[i]);
}
}