CF1620F
给个很好写的方法。
- 二分图等价于没有奇环。
- 题目给出的连边方式是具有偏序关系的,若有 $a \to b, b \to c$,就有 $a \to c$,此时 $a, b, c$ 构成三元环。
- 最长链的长度仅为 $2$。
- $i \neq j, p_i \neq p_j$,其反图也有偏序关系。
- 最长反链等于最小点覆盖,因此其反图($i \to j \iff i < j, p_i < p_j$)可以用两条链覆盖。
- 只要能用两条上升的链覆盖原序列,它就是一个“二分序列”。
- 原序列是排列时,这个条件一定成立,至于不是排列是否满足我没多想。
- dp。
- 朴素的 dp:$f_{i, j, k}$ 表示考虑到 $i$,两个序列的结尾分别是 $j, k$ 是否可行。
- $a_i$ 或 $-a_i$ 是某一个序列的结尾。$f_{i, j, 0/1}$:考虑到 $i$,两个序列的结尾是 $j$ 和 $a_i / -a_i$ 是否可行。
- 值域定义域互换。(要求 dp 是最优性 dp,且交换的维度上的状态的后继状态有包含关系)
- 在 $f_{i, *}$ 上,如果对于 $x < y$(大概是需要全序关系的,一般是大于小于),$f_{i, x}$ 能转移到的所有状态 $f_{i, y}$ 都能转移到,且 $f_{i, x} = f_{i, y}$,则 $x$ 不优,则可以记录每个 $f_{i, x}$ 对应的最优的 $x$。(全序是为了保证这样的 $x$ 唯一。)
- $f_{i, 0/1}$:考虑到 $i$,某个序列的结尾是 $a_{i} / -a_{i}$ 时,另一个序列的结尾的最小值。
- 考虑如何记录路径。发现只要知道是从上一个状态的 $0/1$ 转移过来就行了,因此路径数组的值取
bool
就行。(然而我实现时把这一位的状态压进去了,很没必要) - 枚举当前点取正 / 负,接在哪一个序列后面。贡献式 dp 转移很方便。
- 可以用返回值为
bool
的cmin
减小代码量。
#include <bits/stdc++.h>
const int inf = 1e9;
bool cmin(int &x, int y) { return y < x ? x = y, 1 : 0; }
void solve() {
int n; scanf("%d", &n);
std::vector<int> a(n);
for (int &x : a) scanf("%d", &x);
std::vector<std::vector<int>> f(n + 1, std::vector<int> (2, inf)), path = f;
f[0][0] = f[0][1] = -inf;
for (int i = 0; i < n; i++) {
for (int j = 0; j < 2; j++) {
int cur = i ? a[i - 1] * (2 * j - 1) : -inf;
if (a[i] >= cur && cmin(f[i + 1][1], f[i][j])) path[i + 1][1] = 2 + j;
if (-a[i] >= cur && cmin(f[i + 1][0], f[i][j])) path[i + 1][0] = j;
if (a[i] >= f[i][j] && cmin(f[i + 1][1], cur)) path[i + 1][1] = 2 + j;
if (-a[i] >= f[i][j] && cmin(f[i + 1][0], cur)) path[i + 1][0] = j;
}
}
if (f[n][0] == inf && f[n][1] == inf) return printf("NO\n"), void();
int x = n, y = f[n][0] == inf ? 1 : 0;
std::vector<int> ans(n);
for (; x > 0; --x) {
ans[x - 1] = a[x - 1] * (path[x][y] / 2 * 2 - 1);
y = path[x][y] & 1;
}
printf("YES\n");
for (auto &u : ans) printf("%d ", u);
printf("\n");
}
int main() {
int T; scanf("%d", &T); while (T--) {
solve();
}
}
本文来自博客园,作者:purplevine,转载请注明原文链接:https://www.cnblogs.com/purplevine/p/17914821.html