• 【AT4439】[AGC028E] High Elements(线段树)


    点此看题面

    • 给定一个(1sim n)的排列,要求将它划分为两个子序列(A,B)
    • 用一个长度为(n)(01)串表示划分方案,填(0)的位置划入序列(A),填(1)的位置划入序列(B)
    • 求字典序最小的划分方案,使得(A,B)的前缀最大值个数相等。
    • (nle2 imes10^5)

    暴力枚举每一位

    第一步还是很容易想到的。

    我们暴力枚举每一位,字典序最小肯定是尽可能让当前位填(0)

    由于已经可以确定之前两个序列各自的最大值(t_{A/B})和前缀最大值(c_{A/B}),尝试把当前位置划入序列(A),那么就是要检验对于剩余部分是否存在一个合法的划分方案使得在当前情况下能够使得(A,B)最终前缀最大值个数相等。

    一个重要性质

    显然,原序列的前缀最大值划分之后肯定仍是前缀最大值。

    现在我们想要证明,只要存在解,就必然能构造出一种符合条件的划分方案,使得某个序列中添加的前缀最大值全是原序列的前缀最大值

    其实很好证,就是考虑如果一组合法解中的两个序列中都存在着新的前缀最大值,说明它们在原序列中的前一个前缀最大值肯定在另一个序列之中(因为它们在原序列中的前一个前缀最大值必然比它们大),因此我们交换这两个元素,则它们都将不再是前缀最大值,也就是说两个序列中新的前缀最大值个数都减少了(1),仍然相等。

    推式子

    不妨设(B)中添加的前缀最大值全是原序列的前缀最大值((A)同理)。

    假设暴力枚举到当前位时,原序列之后位置中的前缀最大值还有(c)个。

    再假设(A)中添加的前缀最大值有(p)个是原序列的前缀最大值,有(q)个是新的前缀最大值,则(B)就应该添加了(c-p)个原序列的前缀最大值。

    现在我们要让(c_A+p+q=c_B+c-p),移项得到(2p+q=c_B-c_A+c)

    式子的右边对于当前位置而言是一个定值,那么问题就在于(2p+q)能否恰好取到这个定值。

    考虑(2p+q)实际意义

    显然,(A)中添加的前缀最大值必须要依次递增,且第一项还要大于(t_A)

    考虑我们给添加到(A)的一种前缀最大值取法定一个权值,不妨设原序列的前缀最大值权值为(2),新的前缀最大值权值为(1),则一种取法的权值恰好就是(2p+q)

    而根据它的实际意义,由于我们可以任意删去一种取法中的若干元素,而权值又只有(1)(2),因此如果权值(x)是可以取到的,权值(x-2)也必然能取到,因此我们只要分别求出奇数和偶数情况的最大值,取出和(c_B-c_A+c)奇偶性相同的那个比大小即可。

    这样一来,我们要求的就是从每个点向后的带权(LIS)

    这个东西的预处理,可以直接从后往前扫一遍,用两棵值域线段树优化(DP)求解,每次询问下标比(a_i)大的最大值。

    具体调用时,我们仍然会有询问下标比(t_{A/B})大的最大值操作,这可以直接继续利用之前的线段树,只要每做到一位就把它的值对应位置修改为(-INF)删去即可。

    代码:(O(nlogn))

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 200000
    #define INF (int)1e9
    using namespace std;
    int n,a[N+5],p[N+5];
    namespace FastIO
    {
    	#define FS 100000
    	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
    	char oc,FI[FS],*FA=FI,*FB=FI;
    	Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
    	Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
    }using namespace FastIO;
    class SegmentTree
    {
    	private:
    		#define PT CI l=1,CI r=n+1,CI rt=1
    		#define LT l,mid,rt<<1
    		#define RT mid+1,r,rt<<1|1
    		#define PU(x) (V[x]=max(V[x<<1],V[x<<1|1]))
    		int V[N<<2];
    	public:
    		I void Build(PT) {if(V[rt]=-INF,l==r) return;RI mid=l+r>>1;Build(LT),Build(RT);}//奇数全部赋成-INF(偶数始终可以取0,不需初值)
    		I void U(CI x,CI v,PT) {if(l==r) return (void)(V[rt]=v);RI mid=l+r>>1;x<=mid?U(x,v,LT):U(x,v,RT),PU(rt);}//单点修改
    		I int Q(CI x,PT) {if(x<l) return V[rt];RI mid=l+r>>1;return max(x<mid?Q(x,LT):-INF,Q(x,RT));}//查询下标比x大的最大值
    }S[2];
    I bool Chk(CI i,CI t1,CI t2,CI c1,CI c2,CI c)//检验
    {
    	if(c2-c1+c>=0&&S[(c2-c1+c)&1].Q(t1)>=c2-c1+c) return 1;//假设B中全是原序列的前缀最大值
    	if(c1-c2+c>=0&&S[(c1-c2+c)&1].Q(t2)>=c1-c2+c) return 1;return 0;//假设A中全是原序列的前缀最大值
    }
    int main()
    {
    	RI i,t=0,c=0;for(read(n),i=1;i<=n;++i) read(a[i]),t<a[i]&&(p[i]=1,t=a[i],++c);//求出原序列所有前缀最大值
    	for(S[1].Build(),i=n;i;--i) S[p[i]^1].U(a[i],S[0].Q(a[i])+(p[i]+1)),S[p[i]].U(a[i],S[1].Q(a[i])+(p[i]+1));//线段树优化DP
    	RI t1=0,c1=0,t2=0,c2=0;if(!Chk(1,0,0,0,0,c)) return puts("-1"),0;//判无解
    	for(i=1;i<=n;++i) S[0].U(a[i],-INF),S[1].U(a[i],-INF),putchar(48|//从线段树上删去当前位置贡献
    		(Chk(i+1,max(t1,a[i]),t2,c1+(t1<a[i]),c2,c-=p[i])?(t1<a[i]&&(t1=a[i],++c1),0):(t2<a[i]&&(t2=a[i],++c2),1)));//尽可能插入序列A
    	return 0;
    }
    
    败得义无反顾,弱得一无是处
  • 相关阅读:
    Title
    Title
    Title
    Title
    Python生成随机验证码
    Time模块和datetime模块
    For循环的实质
    Python函数
    集合set
    字符串内置方法的使用
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/AT4439.html
Copyright © 2020-2023  润新知