AGC028E

题意

给出一个长度为 \(n\) 的排列 \(P\)
一个长度为 \(n\) 的 01 串 \(S\) 被成为好的,当且仅当:

  • 按照以下方式构造序列 \(X,Y\)
    • 首先我们让 \(X,Y\) 为空的序列
    • 对于每一个 \(i=1,2,\ldots,N\),按照顺序,如果 \(S_i=0\),在 \(X\) 后面添加 \(P_i\);如果 \(S_i=1\),在 \(Y\) 后面添加 \(P_i\)
  • \(X,Y\) 的前缀最大值的个数相同

判断是否存在一个好的字符串,并且输出字典序最小的那一个。\(n \leq 2 \times 10^5\)

题解

最小化字典序,我们就会考虑如何去判断某个确定的前缀是否合法。

我们将 \(X,Y\) 内的前缀最大值分成两类:原值和新值。原值的意思就是 \(P\) 中的前缀最大值,新值就指的不是 \(P\) 中的前缀最大值。

我们考虑确定好这个前缀后,设 \(X,Y\) 的前缀最大值数量为 \(x,y\),一个结论是:如果这个前缀合法,那么一定有至少一个序列,满足还没填的那一块只有原值。

证明:假设都有新值的话,考虑是 \(a,b\)\(a\)\(X\) 内是新值的原因是,把它挡住的原值被放到了 \(Y\) 里;\(b\) 同理。所以交换 \(a,b\) 后这两个新值都消失了,并且还是保证前缀最大值相同的。所以肯定能把一个序列的新值个数削成 \(0\)

于是我们就可以找一下等量关系:设 \(k\) 表示还没填的 \(P\) 中原值的数量。我们设是 \(A\) 的后面只有原值,那么设 \(B\)\(a\) 个原值,\(b\) 个新值,\(A\) 就有 \(k-a\) 个原值,可以列出等式:

\[\begin{align*} a+b+y &= k-a+x\\ 2a+b &= k+x-y \end{align*} \]

我们考虑找一下 \(2a+b\) 的意义:就是原值的贡献是 \(2\),新值的贡献是 \(1\)。所以问题就在于,能否从 \(B\) 后面选出一个上升的序列,满足拼起来之后还是一个前缀最大值,并且分数是 \(2a+b\)

但是这样如果直接 dp 的话是二维的状态不好 dp。但是我们观察到:如果能选出一个 \(x \geq 2\),一定能选出一个值为 \(x-2\) 的 dp。所以我们只关心当前情况下能选出的最大奇数/偶数分数。那么就可以设 \(f_{i,0/1}\) 表示考虑后缀 \(i\) ,强制选择 \(a_i\),偶数/奇数的最大得分,转移就是:

\[f_{i,x} = \max_{j > i,a_j > a_i} f_{j,x'} \]

从后向前 dp,只需要考虑第二个限制,线段树优化复杂度 \(O(n \log n)\)

不过注意一下我们这里查询的是 \(i \geq x,a_i \geq a_x\) 的最大值,所以最后还是要倒着来一遍线段树的。

\(Y\) 是只有原值的情况类似推式子集合。等式是 \(2a+b = k+y-x\)

启发:

  1. 在做物品权值的时候,如果有性质如果 \(x\) 可以,那么 \(x-a\) 也可以,就可以考虑求出 \(\bmod a = i\) 的最大/最小值来优化。有的时候需要跑最短路(其实这个就是同余最短路,也告诉我们某些背包问题也可以用这个东西优化)
  2. 这个题一开始 \(A,B\) 原值新值都有,我们注意到原值不管在哪里都是前缀最大值,所以我们要考虑能否调整让问题变得简单。

代码

#include <bits/stdc++.h>

#define fi first
#define se second
#define DB double
#define U unsigned
#define P std::pair
#define LL long long
#define LD long double
#define pb emplace_back
#define MP std::make_pair
#define SZ(x) ((int)x.size())
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl

const int MAXN = 2e5 + 5;

struct DS{
    int mx[MAXN<<2];
    DS(){CLR(mx,~0x3f);}
    #define lc ((x)<<1)
    #define rc ((x)<<1|1)

    inline void update(int x,int l,int r,int p,int d){
        if(l == r) return (void)(mx[x] = d);
        int mid = (l + r) >> 1;
        if(p <= mid) update(lc,l,mid,p,d);
        else update(rc,mid+1,r,p,d);
        mx[x] = std::max(mx[lc],mx[rc]);
    }

    inline int query(int x,int l,int r,int L,int R){
        if(L == 0) L = 1;
        if(l == L && r == R) return mx[x];
        int mid = (l + r) >> 1;
        if(R <= mid) return query(lc,l,mid,L,R);
        if(L > mid) return query(rc,mid+1,r,L,R);
        return std::max(query(lc,l,mid,L,mid),query(rc,mid+1,r,mid+1,R));
    }
}tr[2];

int n,a[MAXN];
bool mx[MAXN];
int res[MAXN];

int main(){
    scanf("%d",&n);
    FOR(i,1,n) scanf("%d",a+i);
    int t = 0;
    FOR(i,1,n){
        t = std::max(t,a[i]);
        mx[i] = t == a[i];
    }
    tr[0].update(1,1,n+1,n+1,0);
    ROF(i,n,1){
        int f[2];CLR(f,~0x3f);
        FOR(j,0,1) f[j] = tr[j^mx[i]^1].query(1,1,n+1,a[i],n+1)+1+mx[i];
        FOR(j,0,1) tr[j].update(1,1,n+1,a[i],f[j]);
    }
    int x = 0,y = 0,k = 0,lx = 0,ly = 0;
    FOR(i,1,n) k += mx[i];
    FOR(i,1,n){
        k -= mx[i];
        FOR(j,0,1) tr[j].update(1,1,n+1,a[i],~0x3f3f3f3f);
        // 0
        x += lx < a[i];
        if((x+k-y >= 0 && tr[(x+k-y)%2].query(1,1,n+1,ly,n+1) >= x+k-y) || (k+y-x >= 0 && tr[(k+y-x)%2].query(1,1,n+1,std::max(lx,a[i]),n+1) >= k+y-x)){
            res[i] = 0;lx = std::max(lx,a[i]);
            continue;
        }
        x -= lx < a[i];y += ly < a[i];
        res[i] = 1;ly = std::max(ly,a[i]);
    }
    if(x != y) return puts("-1"),0;
    FOR(i,1,n) putchar('0'+res[i]);
    puts("");
    return 0;
}

posted @ 2021-02-27 17:51  RainAir  阅读(68)  评论(0编辑  收藏  举报