正题
题目链接:https://www.luogu.com.cn/problem/P7988
题目大意
给出一个长度为nnn的排列,开始有一个数字xxx,第一次询问回答x<a1x<a_1x<a1(记为LOLOLO)或者x>a1x>a_1x>a1(记为HIHIHI),然后继续往后问,如果aia_iai不在范围内就不询问,求对于每个k∈[0,n],x=k+0.5k\in [0,n],x=k+0.5k∈[0,n],x=k+0.5时回答串中HILOHILOHILO的个数。
1≤n≤2×1051\leq n\leq 2\times 10^51≤n≤2×105
解题思路
所有数一起考虑,考虑到序列里的每个询问aia_iai只有当数字所在的区间包含aia_iai时才会询问,然后aia_iai会把一个区间成两个。
那么先考虑HIHIHI,假设第iii个询问是HIHIHI,那么首先肯定有x<aix< a_ix<ai,然后有x>aj(j∈[1,i−1],aj≤ai)x>a_j(j\in[1,i-1],a_j\leq a_i)x>aj(j∈[1,i−1],aj≤ai)也就是还要在aia_iai目前在的区间内。
之后考虑这个HIHIHI的下一个能否是LOLOLO,首先它的下一个被询问的位置肯定是在[max{aj},ai][\ max\{a_j\},a_i\ ][ max{aj},ai ]这个范围内的aka_kak。然后我们要的xxx就在[ak,ai][a_k,a_i][ak,ai]范围内。
用setsetset求出每个aia_iai对应的max{aj}max\{a_j\}max{aj}让,然后反过来做用线段树维护aka_kak就好了。
时间复杂度:O(nlogn)O(n\log n)O(nlogn)
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
using namespace std;
const int N=2e5+10;
int n,a[N],l[N],s[N],w[N<<2];
set<int> pre;
void Change(int x,int L,int R,int pos,int val){w[x]=val;if(L==R)return;int mid=(L+R)>>1;if(pos<=mid)Change(x*2,L,mid,pos,val);else Change(x*2+1,mid+1,R,pos,val);return;
}
int Ask(int x,int L,int R,int l,int r){if(L==l&&R==r)return w[x];int mid=(L+R)>>1;if(r<=mid)return Ask(x*2,L,mid,l,r);if(l>mid)return Ask(x*2+1,mid+1,R,l,r);return min(Ask(x*2,L,mid,l,mid),Ask(x*2+1,mid+1,R,mid+1,r));
}
int main()
{scanf("%d",&n);pre.insert(0);pre.insert(n+1);for(int i=1;i<=n;i++){scanf("%d",&a[i]);l[i]=*(--pre.upper_bound(a[i]));pre.insert(a[i]);}for(int i=0;i<=n+1;i++)Change(1,0,n+1,i,n+1);for(int i=n;i>=1;i--){int k=a[Ask(1,0,n+1,l[i],a[i])];if(k){s[a[Ask(1,0,n+1,l[i],a[i])]]++;s[a[i]]--;}Change(1,0,n+1,a[i],i);}for(int i=1;i<=n;i++)s[i]+=s[i-1];for(int i=0;i<=n;i++)printf("%d\n",s[i]);return 0;
}