LibreOJ #3271. 「JOISC 2020 Day1」建筑装饰 4
Description
给定长度为 $2n$ 的两个序列,分别为序列 $\text A$,和序列 $\text B$。
构造一个长度为 $2n$ 的序列 $\text C$。满足以下条件:
- 序列 $\text C$ 的第 $i$ 个数 $C_i$,只能从 $A_i$ 和 $B_i$ 中选取;
- 设 $a$ 为序列 $\text A$ 中元素被选取的次数,$b$ 为序列 $\text B$ 中元素被选取的次数,则 $a = b = n$;
- 该序列是一个单调不下降的序列。
如有多解,任意输出一组解即可,无解输出 $-1$。
Solution
后文请自动将状态中的 $A$ 视作 $0$,$B$ 视作 $1$。
$\mathcal O(n^2)$ 的思路还是很好想的,用 $f_{i, j, A/B}$ 表示前 $i$ 个数,选了 $j$ 个 $\text A$ 中的,最后一个数字选的是 $\text A$ 中的还是 $\text B$ 中的,当前末尾的最小值是多少。转移显然。
改变策略,用 $f_{i, A/B, A/B}$ 表示前 $i$ 个数,最后一个选了 $\text A$ 中的还是 $\text B$ 中的,最大化选 $\text A$ 的个数还是选 $\text B$ 的个数,最多能选多少个 $\text A / \text B$。
我们枚举 $i$ 和 $i - 1$ 的状态,分类讨论。下文的 $\to$ 可以视作「去更新」,也就是一个取 $\max$ 的操作。
- 如果 $A_{i-1} \le A_i$,那么我们试图选择 $A_{i - 1}$ 和 $A_i$,这时可以用 $f_{i - 1, A, A} + 1 \to f_{i, A, A}$(又选了一个 $\text A$),用 $f_{i - 1, A, B} \to f_{i, A, B}$(没有选 $\text B$);
- 如果 $B_{i-1} \le A_i$,那么我们试图选择 $B_{i - 1}$ 和 $A_i$,这时可以用 $f_{i - 1, B, A} + 1 \to f_{i, A, A}$(又选了一个 $\text A$),用 $f_{i - 1, B, B} \to f_{i, A, B}$(没有选 $\text B$);
- 如果 $A_{i-1} \le B_i$,那么我们试图选择 $A_{i - 1}$ 和 $B_i$,这时可以用 $f_{i - 1, A, A} \to f_{i, B, A}$(没有选 $\text A$),用 $f_{i - 1, A, B} + 1 \to f_{i, B, B}$(又选了一个 $\text B$);
- 如果 $B_{i-1} \le B_i$,那么我们试图选择 $B_{i - 1}$ 和 $B_i$,这时可以用 $f_{i - 1, B, A} \to f_{i, B, A}$(没有选 $\text A$),用 $f_{i - 1, B, B} + 1 \to f_{i, B, B}$(又选了一个 $\text B$)。
求出这个有什么作用呢?当 $f_{2n, A, A} \ge n$ 且 $f_{2n, A, B} \ge n$;或者当 $f_{2n, B, A} \ge n$ 且 $f_{2n, B, B} \ge n$ 时必然有解。这很显然,因为只要两者都能选 $\ge n$ 个,我们就让两者都选 $n$ 个好了。
然后逆序构造答案,时刻保证选 $\text A$ 的个数和选 $\text B$ 的个数都 $\ge n$ 就好了。
时间复杂度 $\mathcal O(n)$。
#include <bits/stdc++.h> using namespace std; const int N = 1e6 + 5, A = 0, B = 1; int n, a[N], b[N], f[N][2][2]; char ans[N]; inline void upd(int &x, int y) { if(y > x) x = y; } int main() { scanf("%d", &n); for(int i = 1; i <= n << 1; i++) scanf("%d", &a[i]); for(int i = 1; i <= n << 1; i++) scanf("%d", &b[i]); memset(f, -0x3f, sizeof f); f[1][A][A] = 1; f[1][A][B] = 0; f[1][B][A] = 0; f[1][B][B] = 1; for(int i = 2; i <= n << 1; i++) { if(a[i - 1] <= a[i]) upd(f[i][A][A], f[i - 1][A][A] + 1), upd(f[i][A][B], f[i - 1][A][B]); if(b[i - 1] <= a[i]) upd(f[i][A][A], f[i - 1][B][A] + 1), upd(f[i][A][B], f[i - 1][B][B]); if(a[i - 1] <= b[i]) upd(f[i][B][A], f[i - 1][A][A]), upd(f[i][B][B], f[i - 1][A][B] + 1); if(b[i - 1] <= b[i]) upd(f[i][B][A], f[i - 1][B][A]), upd(f[i][B][B], f[i - 1][B][B] + 1); } int cntA = 0, cntB = 0, lst = INT_MAX; for(int i = n << 1; i; i--) { if(cntA + f[i][A][A] >= n && cntB + f[i][A][B] >= n && a[i] <= lst) cntA++, ans[i] = 'A', lst = a[i]; else if(cntA + f[i][B][A] >= n && cntB + f[i][B][B] >= n && b[i] <= lst) cntB++, ans[i] = 'B', lst = b[i]; else return puts("-1") && 0; } for(int i = 1; i <= n << 1; i++) putchar(ans[i]); return 0; }