CF1508F. Optimal Encoding

给出个排列\(a\),并且给出\(m\)个区间\([L_i,R_i]\)

定义一个排列\(b\)对于某区间\([L,R]\)合法,当且仅当对于所有\(i,j\in [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|\)

\(k\in [1,m]\)分别求答案。

\(n\le 2.5*10^4,q\le 10^5\)


普通的想法:先对每个区间中,如果有\(a_u<a_v\)就连边\((u,v)\)。构出图之后,删掉一些边,使得仍然满足大小关系。如果\((u,v)\)保留,当且仅当不存在\(x\neq u,v\),存在路径\(u\to x\to v\)

结合区间的性质思考问题:假设\(u<v\)。如果\((u,v)\)存在,那么存在同时包含\(u,v\)的区间。在这个条件下,如果\((u,v)\)被删去:如果\([u,v]\)内存在点\(t\)满足\(a_u<a_t<a_v\),显然要删去;否则,存在一条路径\(u\to p_1\to p_2\to \dots p_k\to 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)\neq 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)\)的存在时间,由以下三个参数决定:

  1. 第一次出现区间同时包含\(u,v_i\)的时间\(t_1\)
  2. \((u,v_i)\)第一次被隐藏(即此时找到了点\(t<u\)满足\(t,u,v_i\)在同一个区间,且\(a_u<a_t<a_{v_i}\))的时间\(t_2\)
  3. 第一次出现区间同时包含\((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_j\le t,v_i\le R_j\),也可以二维偏序解决之。

有用的\((u,v)\)只有\(O(n\sqrt q)\)个。证明考虑对于某个区间\([l,r]\),如果\(u,v\)有连边,则\(a_u,a_v\)在区间内的权值离散化后连续。把它变成\([l\pm 1,r \pm 1]\),发现总量的变化量是\(O(1)\)的。假设跑个莫队,那么总数不会超过\(O(n\sqrt q)\)

如何找到有用的\((u,v)\):直接跑莫队,用set维护相对顺序,可以做到\(O(n\sqrt q\lg n)\);也可以回滚莫队,把当前块以及之后的点排序,用链表维护,然后倒着删当前块后面的点(得知每个点加入时的前驱后继)。一开始左端点固定在块头(平常是块尾),右端点右移,在链表中加点,对于每个询问移动左端点过来(每次删掉左端点),询问后再把左端点移动回去。那么这样做除了排序之外是\(O(n\sqrt q)\)

于是总时间\(O(n\sqrt q\lg 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\n",ans);
	}
	return 0;
}
posted @ 2021-04-23 21:31  jz_597  阅读(178)  评论(0编辑  收藏  举报