给出个排列(a),并且给出(m)个区间([L_i,R_i])。
定义一个排列(b)对于某区间([L,R])合法,当且仅当对于所有(i,jin [L,R],a_i<a_j),有(b_i<b_j)。对于某个区间集合合法,当且仅当对于每个区间都合法。
前(k)个区间的答案为:构造一个有向图(G),使得如果排列(b)对于这(k)个区间形成的集合合法,当且仅当对于所有((u,v)in E_G),有(b_u<b_v)。问最小的(|E_G|)。
对(kin [1,m])分别求答案。
(nle 2.5*10^4,qle 10^5)
普通的想法:先对每个区间中,如果有(a_u<a_v)就连边((u,v))。构出图之后,删掉一些边,使得仍然满足大小关系。如果((u,v))保留,当且仅当不存在(x eq u,v),存在路径(u o x o v)。
结合区间的性质思考问题:假设(u<v)。如果((u,v))存在,那么存在同时包含(u,v)的区间。在这个条件下,如果((u,v))被删去:如果([u,v])内存在点(t)满足(a_u<a_t<a_v),显然要删去;否则,存在一条路径(u o p_1 o p_2 o dots p_k o v),必定有(p_k<u),所以(p_k,u,v)在一个区间内,于是就只需要考虑(k=1)的情况。也就是说,找到区间(i)满足([u,v]subseteq [L_i,R_i]),且(L_i)最小,在([L_i,v])中找到(t)满足(a_t>a_u),最小化(a_t),然后判断是否有(a_u<a_t<a_v)。
进一步地可以推断出:从某个点(u)发出的有向边至多两条,一条向左一条向右。以向右的为例,找到包含(u)的区间最大右端点(maxr(u)),在([u,maxr_u])中找到(t)满足(a_t>a_u),(a_t)最小(记作(suc_r(u)))。向左类似地定义(suc_l(u))。((u,suc_l(u)),(u,suc_r(u)))都是边的候选。
一种快速的做法:类似地,换成(a_t<a_u)且(a_t)最大定义(pre_r(u),pre_l(u))。有结论:假设(u<v),((u,v))被保留当且仅当(suc_r(u)=v and pre_l(v)=u)。证明:考虑上面描述的((u,v))被删去的情况,可以发现在那情况中,(t)可以替换(pre_l(v)),所以(pre_l(v) eq u)。于是就可以搞出(O(n^2))的简单做法。由于(n)小并且开7s,这种方法可以随便过。
题解做法:
以右边为例,对于每个(u),找到所有可能成为(suc_r(u))的点,即每个(v)是区间([u,v])满足(a_v>a_u)的(a_v)最小的(v)。显然(v_i<v_{i+1},a_v>a_{i+1})。
显然,随着时间增加,(suc_r(u))右移。
考虑某个((u,v_i))的存在时间,由以下三个参数决定:
- 第一次出现区间同时包含(u,v_i)的时间(t_1)。
- ((u,v_i))第一次被隐藏(即此时找到了点(t<u)满足(t,u,v_i)在同一个区间,且(a_u<a_t<a_{v_i}))的时间(t_2)。
- 第一次出现区间同时包含((u,v_{i+1}))的时间(t_3)。
其存在时间为([t_1,min(t_2,t_3)))。
(u,v_i)第一次被同时包含的时间可以搞个二维偏序求出。至于(t_2),分别求出左右的(v_i)序列,用类似归并的方式搞。对于右边的某个(v_i)来说,可能的(t)是左边序列(位置递减)中一段后缀,只需要取后缀的第一个作为(t)。于是(v_i)对应的(t)是一定的,接下来要找到最小的(j)满足(L_jle t,v_ile R_j),也可以二维偏序解决之。
有用的((u,v))只有(O(nsqrt q))个。证明考虑对于某个区间([l,r]),如果(u,v)有连边,则(a_u,a_v)在区间内的权值离散化后连续。把它变成([lpm 1,r pm 1]),发现总量的变化量是(O(1))的。假设跑个莫队,那么总数不会超过(O(nsqrt q))。
如何找到有用的((u,v)):直接跑莫队,用set
维护相对顺序,可以做到(O(nsqrt qlg n));也可以回滚莫队,把当前块以及之后的点排序,用链表维护,然后倒着删当前块后面的点(得知每个点加入时的前驱后继)。一开始左端点固定在块头(平常是块尾),右端点右移,在链表中加点,对于每个询问移动左端点过来(每次删掉左端点),询问后再把左端点移动回去。那么这样做除了排序之外是(O(nsqrt q))。
于是总时间(O(nsqrt qlg n))。
using namespace std;
#include <bits/stdc++.h>
#define N 25005
#define M 100005
int n,m;
int a[N];
int lmin[N],rmax[N];
int suc[N][2],pre[N][2];
int ans;
void work(int l,int r){
for (int i=l;i<=r;++i){
for (int &j=rmax[i];j<=r;++j)
if (a[j]>a[i]){
int &t=suc[i][0];
if (a[j]<a[t])
ans+=(pre[j][1]==i)-(pre[t][1]==i),t=j;
}
else{
int &t=pre[i][0];
if (a[j]>a[t])
ans+=(suc[j][1]==i)-(suc[t][1]==i),t=j;
}
for (int &j=lmin[i];j>=l;--j)
if (a[j]>a[i]){
int &t=suc[i][1];
if (a[j]<a[t])
ans+=(pre[j][0]==i)-(pre[t][0]==i),t=j;
}
else{
int &t=pre[i][1];
if (a[j]>a[t])
ans+=(suc[j][0]==i)-(suc[t][0]==i),t=j;
}
}
}
int main(){
scanf("%d%d",&n,&m);
for (int i=1;i<=n;++i)
scanf("%d",&a[i]);
a[0]=n+1,a[n+1]=0;
for (int i=1;i<=n;++i){
lmin[i]=i-1,rmax[i]=i+1;
suc[i][0]=suc[i][0]=0;
pre[i][0]=pre[i][1]=n+1;
}
for (int i=1;i<=m;++i){
int l,r;
scanf("%d%d",&l,&r);
work(l,r);
printf("%d
",ans);
}
return 0;
}