zoukankan      html  css  js  c++  java
  • CF1508F. Optimal Encoding

    给出个排列(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))的存在时间,由以下三个参数决定:

    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_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;
    }
    
  • 相关阅读:
    K好数
    最大最小公倍数
    十六进制转十进制
    利用malloc定义数组
    01字串
    ubuntu 14.04 下jdk和sdk+eclipse 的配置
    Mysql3
    求最大连续子串
    UC笔试
    java实现随机洗牌算法
  • 原文地址:https://www.cnblogs.com/jz-597/p/14695626.html
Copyright © 2011-2022 走看看