赛时寄了,然后本人很气愤,于是写下此篇题解。
(没关系,这可是 \({\color{black}j}{\color{red}{iangly}}\) 江老板都没有做出来的绝世大好题啊!!!)
然而看过题解之后 \(O(n)\) 算法还是一概不会,于是只能思维不够 DS 来凑,然而 D 题撑死也不会有什么奇葩东西,所以就选择了线段树板子来做区间最大和最小值的查询(甚至是静态不带修)(然而是线段树板子打太多导致现在不是很能在静态情况下考虑使用更简单的 ST 表)。
言归正传:我们考虑何时能够连边,题意告诉我们,只有最大值和最小值分别分布在区间两端点处时才可以连边,所以说我们假设 \(a_i=n\) 的位置 \(i\) 不在整个序列的两端,那么我们发现 \(i\) 这个位置把整个序列就好像一道天堑一样划成了两部分,两部分之间可以发现总是无法连边的,而从一部分到另一部分总要经过 \(i\) 这个位置。
我们于是把 \(i\) 这样必经的位置称为“枢纽点”。
我们再考察 \(i\) 左右两边的部分,以左边为例,左边的最小值所在位置同样是一个“枢纽点”,它的两边无法连边,从一边到另一边必须经过“枢纽点”,而且,还有一件重要的事情是,这个最小值的“枢纽点”恰好(而且是一定)可以和 \(n\) 的“枢纽点”连边。
所以最小值“枢纽点”的右半部分就不用再考虑了,显然走到它里面的节点会使答案更劣。
所以我们可以以此类推下去,从 \(n\) 对应的的枢纽点出发,找其左半部分的最小值,作为新的“枢纽点”,连边(答案 +1 ),然后再在最小值对应的“枢纽点”左边找一个最大值……再找一个最小值……,如此往复循环下去,直到位置 1 作为最后一个要找的“枢纽点”被连边。
对于 \(n\) 对应的的枢纽点的右半部分,处理方式同理。
int a[250010]; int pos[250010]; int mx[250010<<2]; int mn[250010<<2]; void pushup(int x){ mx[x]=max(mx[x<<1],mx[x<<1|1]); mn[x]=min(mn[x<<1],mn[x<<1|1]); } void build(int x,int l,int r){ if(l==r){ mx[x]=mn[x]=a[l]; return; } //mx[x]=0;mn[x]=0x3f3f3f3f; int mid=(l+r)>>1; build(x<<1,l,mid); build(x<<1|1,mid+1,r); pushup(x); } pii query(int x,int l,int r,int tl,int tr){ if(tl<=l&&r<=tr){ return mp(mx[x],mn[x]); } int mid=(l+r)>>1; pii res=mp(0,0x3f3f3f3f); if(tl<=mid){ pii tmp=query(x<<1,l,mid,tl,tr); res.first=max(res.first,tmp.first); res.second=min(res.second,tmp.second); } if(mid<tr){ pii tmp=query(x<<1|1,mid+1,r,tl,tr); res.first=max(res.first,tmp.first); res.second=min(res.second,tmp.second); } return res; } int T; int n; void main(){ cin>>T; while(T--){ cin>>n; for(int i=1;i<=n;i++){ cin>>a[i]; pos[a[i]]=i; } int ans=0; build(1,1,n); int ps=pos[n]-1,fd=1; while(ps){ ans++; pii tmp=query(1,1,n,1,ps); ps=(fd)?(pos[tmp.second]-1):(pos[tmp.first]-1); fd^=1; } ps=pos[n]+1,fd=1; while(ps<=n){ ans++; pii tmp=query(1,1,n,ps,n); ps=(fd)?(pos[tmp.second]+1):(pos[tmp.first]+1); fd^=1; } cout<<ans<<endl; } }