题意
给出一个长度为 (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) 个原值,可以列出等式:
我们考虑找一下 (2a+b) 的意义:就是原值的贡献是 (2),新值的贡献是 (1)。所以问题就在于,能否从 (B) 后面选出一个上升的序列,满足拼起来之后还是一个前缀最大值,并且分数是 (2a+b)。
但是这样如果直接 dp 的话是二维的状态不好 dp。但是我们观察到:如果能选出一个 (x geq 2),一定能选出一个值为 (x-2) 的 dp。所以我们只关心当前情况下能选出的最大奇数/偶数分数。那么就可以设 (f_{i,0/1}) 表示考虑后缀 (i) ,强制选择 (a_i),偶数/奇数的最大得分,转移就是:
从后向前 dp,只需要考虑第二个限制,线段树优化复杂度 (O(n log n))。
不过注意一下我们这里查询的是 (i geq x,a_i geq a_x) 的最大值,所以最后还是要倒着来一遍线段树的。
(Y) 是只有原值的情况类似推式子集合。等式是 (2a+b = k+y-x)。
启发:
- 在做物品权值的时候,如果有性质如果 (x) 可以,那么 (x-a) 也可以,就可以考虑求出 (mod a = i) 的最大/最小值来优化。有的时候需要跑最短路(其实这个就是同余最短路,也告诉我们某些背包问题也可以用这个东西优化)
- 这个题一开始 (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;
}