BZOJ 2120 数颜色 分块+二分

来源:互联网 发布:直播吧软件下载 编辑:程序博客网 时间:2024/05/16 17:25

点击打开链接

题意:n个数,m个操作n,m<=1e4 
1.查询[l,r]区间内有多少不同的数字
2.单点修改 (不超过1e3次)
如果利用线段树 单点更新时把x变成y后 不容易更新出[l,r]内不同数个数
此时用分块来做,记录每一个数它的上一个位置,不存在则记为0,判断[l,r]内不同的个数,如果a[i]第一次出现在[l,r]内,则ans++ 
更新次数<=1e3,则暴力更新n个数的b[i]和pre[i]即可,修改复杂度O(n) 
若查询区间[l,r] 头尾两块直接判断b[i]<l是否成立,对中间每块的b[i]排序后二分,找到最后一个b[i]<v的位置即可  查询复杂度(sqrt(n)*logn)

#include <bits/stdc++.h>using namespace std;const int N=1e4+20;const int M=1e6+20;int n,m,a[N];int num,block,pos[N],l[N],r[N]; //num为分块的个数,block为每块的大小,pos[i]为i属于哪一块//l[i],r[i]分别为第i块左右边界int b[N],last[M],pre[N];//b,pre都是记录上一次出现位置 void reset(int x){int l=(x-1)*block+1,r=min(n,x*block);for(int i=l;i<=r;i++)pre[i]=b[i];sort(pre+l,pre+r+1);//查询时用到}void build(){block=int(sqrt(n)+log(2*n)/log(2));num=n/block;if(n%block) num++;for(int i=1;i<=n;i++){b[i]=last[a[i]];//bi:i上一次出现位置 last[a[i]]=i;//ai,最后一次出现位置  pos[i]=(i-1)/block+1;}for(int i=1;i<=num;i++)reset(i);} int find(int x,int v){int l=(x-1)*block+1,r=min(x*block,n); int first=l;while(l<=r){int mid=(l+r)>>1;if(pre[mid]<v) l=mid+1;else r=mid-1;}return l-first;}int ask(int l,int r){int ans=0;if(pos[l]==pos[r]){for(int i=l;i<=r;i++)if(b[i]<l)//上一次出现在l之前 ans++;}else{//第一块后最后一块 for(int i=l;i<=block*pos[l];i++)if(b[i]<l) ans++;for(int i=block*(pos[r]-1)+1;i<=r;i++)if(b[i]<l) ans++;}//中间的块 for(int i=pos[l]+1;i<pos[r];i++)ans+=find(i,l);return ans;}void change(int x,int v){for(int i=1;i<=n;i++)last[a[i]]=0;a[x]=v;for(int i=1;i<=n;i++)//修改操作不多于1e3 {int t=b[i];b[i]=last[a[i]];if(t!=b[i]) reset(pos[i]);last[a[i]]=i;}}int main(){while(cin>>n>>m){for(int i=1;i<=n;i++)scanf("%d",&a[i]);build();char op[5];int x,y;for(int i=1;i<=m;i++){scanf("%s%d%d",op,&x,&y);if(op[0]=='Q'){//[x,y]中有多少个不同的数printf("%d\n",ask(x,y));}elsechange(x,y);}}return 0;}


0 0
原创粉丝点击