题解【NOIO2022 Round1 丹钓战】(线段树,扫描线)
Description
给出 \(n\) 个二元组 \((a_i,b_i)\) 和 \(q\) 个询问 \([l_i,r_i]\),对于每个询问回答将区间 \([l_i,r_i]\) 内的二元组依次加入满足相邻两数 \(a\) 值不同、\(b\) 值递减的单调栈的的过程中,有多少次将栈弹空。
\(1\leq n,q\leq 5\times 10^5\)
Solution
发现左端点相同的询问加入元素的过程是相同的,考虑将询问离线,处理一起左端点相同的询问。
设 \(p_{l,i}\) 表示以 \(l\) 为左端点的情况下加入二元组 \((a_i,b_i)\) 时是否能将栈弹空,那么 \([l,r]\) 的答案即为 \(\sum_{i=l}^rp_{l,i}\)。考虑以 \(l\) 为左端点相较于以 \(l+1\) 为左端点产生了怎样的变化。设 \((a_i,b_i)\) 为将 \((a_l,b_l)\) 从栈中弹出的元素,那么有:
- \(\forall j\in(l,i),p_{l,i}=0\)。考虑反证,若 \(j_0\in(l,i)\) 能将栈弹空,则 \(j_0\) 就会将 \(l\) 弹出,与定义不符。
- \(\forall j\in[i,n],p_{l,i}=p_{l+1,i}\)。\(i\) 若能将 \(l\) 弹出,则也能将 \(l+1\) 弹出。那么加入 \(i\) 后以 \(l\) 为左端点和以 \(l+1\) 为左端点栈中都只剩 \((a_i,b_i)\),此后过程完全一致。
如何找到 \(i\)?首先 \(i\) 一定满足 \(p_{l+1,i}=1\)。考虑要么 \(a_i=a_l\),这种情况对于每个 \(a\) 值开一个 set
,存放 \(a_j=a_l\) 且 \(p_{l+1,j}=1\) 的所有 \(j\),取 \(a_l\) 对应的 set
中的最小值即为 \(i\);另外一种情况就是 \(b_l\leq b_i\),那么我们可以用线段树维护 \(p_{l+1,i}\) 和区间中满足 \(p_{l+1,i}=1\) 的最大 \(b_i\),的通过线段树上二分找到 \(i\)。
对于修改,需要实现单点修改和区间推平。这里有个特殊之处,一个位置只会被插入一次和推平一次,如果用线段树维护的话推平不需要懒标记,暴力推平即可,也因为这个性质,所以也可以用珂朵莉树继续维护,时间复杂度也是正确的。推平的时候要同时修改 set
。
时间复杂度为 \(O((n+q)\log n)\)。
struct que{ int r,id; que(int r=0,int id=0):r(r),id(id) {} };
int n,q;
int a[500005],b[500005],ans[500005];
vector<que> m[500005];
set<int> s[500005];
struct node{
int l,r,mxb,sum;
}t[500005<<2];
inline void up(int x){ t[x].mxb=max(t[x<<1].mxb,t[x<<1|1].mxb); t[x].sum=t[x<<1].sum+t[x<<1|1].sum; }
void build(int x,int l,int r){
t[x].l=l,t[x].r=r;
if(l==r) return;
const int mid=l+r>>1;
build(x<<1,l,mid),build(x<<1|1,mid+1,r);
}
void insert(int x,const int id){
if(t[x].l==t[x].r) return t[x].sum=1,t[x].mxb=b[id],void();
const int mid=t[x].l+t[x].r>>1;
if(id<=mid) insert(x<<1,id);
else insert(x<<1|1,id);
up(x);
}
void assign(int x,const int sl,const int sr){
if(sl>sr) return;
if(t[x].l==t[x].r) return t[x].mxb=t[x].sum=0,s[a[t[x].l]].erase(t[x].l),void();
const int mid=t[x].l+t[x].r>>1;
if(sl<=mid&&t[x<<1].sum) assign(x<<1,sl,sr);
if(sr>mid&&t[x<<1|1].sum) assign(x<<1|1,sl,sr);
up(x);
}
int bound(int x,const int k){
if(t[x].l==t[x].r) return t[x].l;
if(t[x<<1].mxb>=k) return bound(x<<1,k);
if(t[x<<1|1].mxb>=k) return bound(x<<1|1,k);
return n+1;
}
int query(int x,const int sl,const int sr){
if(sl<=t[x].l&&sr>=t[x].r) return t[x].sum;
const int mid=t[x].l+t[x].r>>1; int ret=0;
if(sl<=mid) ret+=query(x<<1,sl,sr);
if(sr>mid) ret+=query(x<<1|1,sl,sr);
return ret;
}
signed main(){
//system("fc stack4.out stack4.ans");
file();
n=read(),q=read();
for(rg int i=1;i<=n;++i) s[i].insert(n+1);
for(rg int i=1;i<=n;++i) a[i]=read();
for(rg int i=1;i<=n;++i) b[i]=read();
for(rg int i=1,l,r;i<=q;++i) l=read(),r=read(),m[l].epb(que(r,i));
build(1,1,n);
for(rg int i=n;i;--i){
const int sr=min(bound(1,b[i]),*s[a[i]].begin());
assign(1,i+1,sr-1); insert(1,i); s[a[i]].insert(i);
for(que j:m[i]) ans[j.id]=query(1,i,j.r);
}
for(rg int i=1;i<=q;++i) printf("%d\n",ans[i]);
return 0;
}