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 imes 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) 个原值,可以列出等式:

[egin{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) 也可以,就可以考虑求出 (mod 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;
}

原文地址:https://www.cnblogs.com/rainair/p/14456523.html