• 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;
    }
    
    
  • 相关阅读:
    pycharm中python文件名使用中划线导致无法自动导入
    (笔记)Ubuntu20.04更换软件源
    (笔记)ROS学习——rosdep update 超时解决方法
    (笔记)国内如何访问GitHub
    java基于TreeMap或ConcurrentSkipListMap实现数据的范围查找
    数字正则式(整数、小数、负数、保留两位小数等)
    写了一个简易的本地缓存fastmap,支持键过期和键排序等
    Timer和ScheduledThreadPoolExecutor的区别及源码分析
    Jenkins Unstable 状态解释
    微信小程序部署流程
  • 原文地址:https://www.cnblogs.com/rainair/p/14456523.html
Copyright © 2020-2023  润新知