「BZOJ3339」Rmq Problem(5366)
题目描述
输入
输出
样例输入
7 5
0 2 1 0 1 3 2
1 3
2 3
1 4
3 6
2 7
提示
这个题说来也挺有意思的
当时集训的时候遇到了一道类似的题,但是题意与此不同,我太菜了,理解成了这个题233结果爆零(蒟蒻咆哮:“唉我AC呢!?”)
所以就把以前写的码翻了出来,交了上去,果然A了2333
当时的写法是这样的:
首先处理出每个数下一次出现的位置,这样每个数字就处理成了 这段区间内这个数没有出现过,数量是O(n)级别的
那么一个询问区间的答案就是所有套在它外面的不出现区间的最小值
具体实现就是首先离线,将“询问区间”和“不出现区间”在一起按照左端点排序,如果相同则优先处理“不出现区间”
此时维护一个线段树,位置表示的是区间的右端点,这个位置上存的是:l小于等于当前扫到的L,且以这个位置为r的区间中数最小的是啥
那么如果扫到了一个提问,那么答案就是(R,N)这个区间内最小值
当时写码的时候有点蒙圈,实际上好像写个单点修改就好了?
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #define maxn 200005 5 #define inf n+1 6 using namespace std; 7 int a[maxn],nxt[maxn],top; 8 int n,m,s[maxn*4],tag[maxn*4]; 9 struct line{ 10 int l,r,x,o; 11 }q[maxn*10]; 12 bool cmp(line A,line B){if(A.l!=B.l)return A.l<B.l;else return A.o<B.o;} 13 int ans[maxn]; 14 void build(int x,int l,int r){ 15 s[x]=tag[x]=inf; 16 if(l==r)return; 17 int mid=(l+r)/2; 18 build(x+x,l,mid); 19 build(x+x+1,mid+1,r); 20 } 21 void pushdown(int x,int l,int r){ 22 if(l==r){ 23 tag[x]=inf; 24 return; 25 } 26 s[x+x]=min(s[x+x],tag[x]); 27 tag[x+x]=min(tag[x+x],tag[x]); 28 s[x+x+1]=min(s[x+x+1],tag[x]); 29 tag[x+x+1]=min(tag[x+x+1],tag[x]); 30 tag[x]=inf; 31 return; 32 } 33 void add(int x,int l,int r,int L,int R,int k){ 34 if(l==L&&r==R){ 35 s[x]=min(s[x],k); 36 tag[x]=min(tag[x],k); 37 return; 38 } 39 pushdown(x,l,r); 40 int mid=(l+r)/2; 41 if(R<=mid)add(x+x,l,mid,L,R,k); 42 else if(L>mid)add(x+x+1,mid+1,r,L,R,k); 43 else add(x+x,l,mid,L,mid,k),add(x+x+1,mid+1,r,mid+1,R,k); 44 s[x]=min(s[x+x],s[x+x+1]); 45 } 46 int query(int x,int l,int r,int L,int R){ 47 if(l==L&&r==R)return s[x]; 48 pushdown(x,l,r); 49 int mid=(l+r)/2; 50 if(R<=mid)return query(x+x,l,mid,L,R); 51 else if(L>mid)return query(x+x+1,mid+1,r,L,R); 52 else return min(query(x+x,l,mid,L,mid),query(x+x+1,mid+1,r,mid+1,R)); 53 } 54 int main(){ 55 scanf("%d%d",&n,&m); 56 build(1,1,n); 57 nxt[0]=n+1; 58 for(int i=1;i<=n;i++){ 59 scanf("%d",&a[i]); 60 nxt[i]=n+1; 61 } 62 nxt[n+1]=n+1; 63 for(int i=n;i>=1;i--){ 64 if(nxt[a[i]]-i-1>0){ 65 q[++top].o=0; 66 q[top].l=i+1; 67 q[top].r=nxt[a[i]]-1; 68 q[top].x=a[i]; 69 } 70 nxt[a[i]]=i; 71 } 72 for(int i=0;i<=n+1;i++) 73 if(nxt[i]-1>0){ 74 q[++top].o=0; 75 q[top].l=1; 76 q[top].r=nxt[i]-1; 77 q[top].x=i; 78 } 79 for(int i=1,l,r;i<=m;i++){ 80 scanf("%d%d",&l,&r); 81 q[++top].o=1; 82 q[top].l=l; 83 q[top].r=r; 84 q[top].x=i; 85 } 86 sort(q+1,q+1+top,cmp); 87 for(int i=1;i<=top;i++){ 88 if(q[i].o==0) 89 add(1,1,n,q[i].r,q[i].r,q[i].x); 90 else{ 91 int A=query(1,1,n,q[i].r,n); 92 ans[q[i].x]=A; 93 } 94 } 95 for(int i=1;i<=m;i++)printf("%d\n",ans[i]); 96 return 0; 97 }
不过这个题还有在线的主席树做法
我们维护一个线段树,以数值为位置,里面存的是目前为止这个数最近一次出现的位置
那么询问一个区间l r,就找r之前都加入了的线段树,找里面出现位置小于l且最小的数
这个可以通过在线段树上跳跃实现,具体看代码吧
这是我写(抄)的第一个主席树,感觉还是不太明了的样子。。。இ௰இ
1 #include<cstdio> 2 #include<algorithm> 3 #define maxn 200005 4 using namespace std; 5 int n,m,cnt; 6 int rt[maxn],a[maxn],b[maxn]; 7 struct tree{ 8 int l,r,Min; 9 }t[22*maxn]; 10 int insert(int K,int X,int I,int l,int r){ 11 t[++cnt]=t[K]; 12 K=cnt; 13 if(l==r){ 14 t[K].l=t[K].r=0; 15 t[K].Min=I; 16 return cnt; 17 } 18 int mid=(l+r)>>1; 19 if(X<=mid) 20 t[K].l=insert(t[K].l,X,I,l,mid); 21 else 22 t[K].r=insert(t[K].r,X,I,mid+1,r); 23 t[K].Min=min(t[t[K].l].Min,t[t[K].r].Min); 24 return K; 25 } 26 int query(int k,int X,int l,int r){ 27 if(l==r)return l; 28 int mid=(l+r)>>1; 29 if(t[t[k].l].Min<X) 30 return query(t[k].l,X,l,mid); 31 else return query(t[k].r,X,mid+1,r); 32 } 33 int main(){ 34 scanf("%d%d",&n,&m); 35 rt[0]=0; 36 t[0]=(tree){0,0,0}; 37 for(int i=1;i<=n;i++){ 38 scanf("%d",&a[i]); 39 if(a[i]<=n) 40 rt[i]=insert(rt[i-1],a[i],i,0,n); 41 else rt[i]=rt[i-1]; 42 } 43 for(int i=1,l,r;i<=m;i++){ 44 scanf("%d%d",&l,&r); 45 printf("%d\n",query(rt[r],l,0,n)); 46 } 47 return 0; 48 }