代码源NOIP DAY2 T1 LIS和LDS题解
题意
给你两个数组
a
1
,
a
2
,
.
.
.
,
a
n
a_1,a_2,...,a_n
a1,a2,...,an 和
b
1
,
b
2
,
.
.
.
,
b
n
b_1, b_2, ..., b_n
b1,b2,...,bn。
你需要构造一个
1
1
1 到
n
n
n 的排列
p
p
p,满足对于所有
i
i
i:
- 以第 i i i 位 结尾 的最长上升子序列长度为 a i a_i ai。
- 以第 i i i 位 开头 的最长下降子序列长度为 b i b_i bi。
数据保证存在一组解。
1 ≤ n ≤ 2 × 1 0 5 1 \leq n \leq 2 \times 10^5 1≤n≤2×105。
分析
首先考虑给你一个数组 p p p 怎样求解最长上升子序列:
有一种做法是 f i = max j < i f j + 1 ( p j < p i ) f_{i} = \max\limits_{j < i} f_{j} + 1(p_j < p_i) fi=j<imaxfj+1(pj<pi)。
但是这样做对于给定 f f f 数组(即 a a a 数组)时,无法确定一些位置之间的大小关系。
另一种做法是我们从前往后求出
f
i
f_i
fi,假设当前
f
i
=
x
f_i = x
fi=x,那么我们另外开一个数组
g
x
g_x
gx 代表长度为
x
x
x 的最长上升子序列的末尾最小值。
那么此时
g
x
g_x
gx 数组显然单调递增。因为如果出现了
g
x
+
1
<
g
x
g_{x + 1} < g_{x}
gx+1<gx,那么我们把
g
x
+
1
g_{x + 1}
gx+1 所在的长度为
x
+
1
x + 1
x+1 的最长上升子序列去掉最后一个数,会得到一个长度为
x
x
x 的上升子序列。而这个子序列的末尾元素一定比
g
x
g_x
gx 要小。
那么每次求
f
i
f_i
fi 就可以在
g
g
g 数组上二分最后一个小于
p
i
p_i
pi 的位置,然后就把
g
f
i
g_{f_i}
gfi 更新成
p
i
p_i
pi。
能够这样更新的原因是 p i p_i pi 一定比原来的 g f i g_{f_i} gfi 更小。因为如果更大的话 f i f_i fi 可以接上前面一个长度为 f i f_i fi 的子序列,那么 f i f_i fi 的值应该等于 f i + 1 f_i + 1 fi+1,与条件矛盾。
那么如果我们知道了最终的 f f f 数组,可以按照上述过程得到一些数的大小关系。例如一个 f i = 2 f_i = 2 fi=2 的位置 i i i 上面的数应该 大于之前最靠后的 f i ′ = 1 f_{i'} = 1 fi′=1 的位置 i ′ i' i′ 上的数,小于之前最靠后的 f i ′ ′ = 2 f_{i''} =2 fi′′=2 的位置 i ′ ′ i'' i′′ 上的数。
那么我们可以用一条 有向边 来表示这样的大小关系。可以连一条 i ′ i' i′ 指向 i i i 的有向边和一条 i i i 指向 i ′ ′ i'' i′′ 的有向边。
并且这样的 大小关系限制 是 f i f_i fi 数组的形态的 充要条件。
只要能够满足这样的大小限制,就一定能得到 f f f 数组。对于本题而言,根据 a i a_i ai 我们可以连出一些有向边。根据 b i b_i bi 也可以连出一些有向边。我们把图建出来跑一遍拓扑排序即可。
具体的连边方式,以下只说明 a i a_i ai, b i b_i bi 只需要倒着扫描数组即可:
- 连一条 l s t a i − 1 lst_{a_i - 1} lstai−1 指向 i i i 的有向边。连一条 i i i 指向 l s t a i lst_{a_i} lstai 的有向边。
- 将 l s t a i lst_{a_i} lstai 更新为 i i i。
拓扑排序每次拿出的队头位置
p
p
p,令
p
p
p 上的数为还没填的最小数即可。
时间复杂度
O
(
n
)
O(n)
O(n)。
CODE:
#include<bits/stdc++.h>
#define pb emplace_back
using namespace std;
const int N = 2e5 + 10;
int n, a[N], b[N];
int lst[N];
int in[N];
vector< int > E[N];
int ans[N], rk;
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
for(int i = 1; i <= n; i ++ ) scanf("%d", &b[i]);
for(int i = 1; i <= n; i ++ ) {
int o = lst[a[i] - 1];
int p = lst[a[i]];
if(o) {
E[o].pb(i);
in[i] ++;
}
if(p) {
E[i].pb(p);
in[p] ++;
}
lst[a[i]] = i;
}
memset(lst, 0, sizeof lst);
for(int i = n; i >= 1; i -- ) {
int o = lst[b[i] - 1];
int p = lst[b[i]];
if(o) {
E[o].pb(i);
in[i] ++;
}
if(p) {
E[i].pb(p);
in[p] ++;
}
lst[b[i]] = i;
}
queue< int > q;
for(int i = 1; i <= n; i ++ ) {
if(in[i] == 0) q.push(i);
}
while(!q.empty()) {
int u = q.front(); q.pop();
ans[u] = ++ rk;
for(auto v : E[u]) {
in[v] --;
if(!in[v]) q.push(v);
}
}
for(int i = 1; i <= n; i ++ ) printf("%d ", ans[i]);
return 0;
}