E. Messenger Simulator
题意:一开始有一个1到n的数组,然后m个操作,操作输入ai,将ai移动到数组的第一个位置,求在m个操作的过程中每个数的最小位置和最大位置。
思路:首先maxl[i],maxr[i]代表每个数的最小和最大位置,进行初始化,然后遍历一遍操作a数组,然后当第一次碰到一个数时,这个数的maxl即为1,然后maxr即为之前出现过的数中小于这个数的个数+这个数初始的位置(不重复计数),如果当第二次遇到某个数问题就转化成在第一次到第二次这段时间里有几个不同的数当了队首,这个一开始想用主席树,可是发现根本没法处理,然后忽然想到了每个相同的数有意义的就是那个数最后出现的那次,就像之前做线段树的题,不重复计数的话r边界依次增加则只有某个数最后一次出现时才有意义,这样就可以数状数组维护位置了,如果某个数第二次出现,则第二次的位置加1,第一次出现的位置减1,这样就可以解决这个问题了,注意的是维护位置的树状数组add操作应该while(p<=m),因为位置是1-m。最后的时候再处理一遍所有的值即可。
#include<iostream>
#include<cstdio>
using namespace std;
const int MAX_N=301000;
int a[MAX_N],maxl[MAX_N],maxr[MAX_N];
int vis[MAX_N];
int sum[MAX_N],n,m,sum1[MAX_N];
void add(int p,int x){
while(p<=n){
sum[p]+=x;
p+=p&-p;
}
}
int ask(int p){
int ans=0;
while(p){
ans+=sum[p];
p-=p&-p;
}
return ans;
}
void add1(int p,int x){
while(p<=m){
sum1[p]+=x;
p+=p&-p;
}
}
int ask1(int p){
int ans=0;
while(p){
ans+=sum1[p];
p-=p&-p;
}
return ans;
}
int main(void){
int i,j;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++){
maxl[i]=i;
maxr[i]=i;
}
for(i=1;i<=m;i++){
scanf("%d",&a[i]);
if(!vis[a[i]]){
vis[a[i]]=i;
maxl[a[i]]=1;
maxr[a[i]]=a[i]+ask(n)-ask(a[i]);
add(a[i],1);
add1(i,1);
}
else{
maxr[a[i]]=max(maxr[a[i]],ask1(i)-ask1(vis[a[i]])+1);
add1(vis[a[i]],-1);
add1(i,1);
vis[a[i]]=i;
}
}
for(i=1;i<=n;i++){
if(!vis[i]){
maxr[i]=i+ask(n)-ask(i);
}
else{
maxr[i]=max(maxr[i],ask1(m)-ask1(vis[i])+1);
}
//cout<<vis[i]<<" vis\n";
printf("%d %d\n",maxl[i],maxr[i]);
}
return 0;
}