不难发现本题贪心是不好做的,可以考虑 \(dp\)。
首先的一个想法就是令 \(dp_{i, j, k, l}\) 表示当前选到第 \(i\) 个位置,当前第一个序列选了 \(j\) 个数,当前第一个序列最后一个元素为 \(k\),第二个序列最后一个元素为 \(l\) 是否合法。这样的话转移十分显然,但复杂度过高了。进一步我们可以发现 \(k, l\) 中一定有一个是 \(a_i\),于是我们可以令 \(dp_{i, j, k, 0 / 1}\) 表示当前选到第 \(i\) 个位置,当前第一个序列选了 \(j\) 个数,选择 \(a_i\) 为结尾的另一个序列的结尾为 \(k\),\(i\) 是第一个序列还是第二个序列选择是否合法。这样的转移也很显然,不再赘述。
下面这个操作就非常骚了,我们可以发现我们 \(dp\) 的值都是 \(0 / 1\),这样感觉非常的亏,我们能不能将状态中的某一维设计到 \(dp\) 值中去呢?答案是可以的,我们令 \(dp_{i, j, 0 / 1}\) 表示当前选到第 \(i\) 个位置,第一个序列选了 \(j\) 个数,\(a_i\) 是第一个还是第二个序列选时另一个序列结尾元素的最小值。那么我们就有转移:
\(dp_{i, j, 0} = \min\{dp_{i, j, 0}, dp_{i - 1, j - 1, 0}\}(a_i > a_{i - 1})\)
\(dp_{i, j, 0} = \min\{dp_{i, j, 0}, a_{i - 1}\}(a_i > dp_{i - 1, j - 1, 1})\)
\(dp_{i, j, 1} = \min\{dp_{i, j, 1}, dp_{i - 1, j, 1}\}(a_i > a_{i - 1})\)
\(dp_{i, j, 1} = \min\{dp_{i, j, 1}, a_{i - 1}\}(a_i > dp_{i - 1, j, 0})\)
最终只需判断 \(dp_{n, \frac{n}{2}, 0 / 1} \ne \infty\) 即可。
#include<bits/stdc++.h>
using namespace std;
#define N 2000 + 5
#define inf 1000000000
#define rep(i, l, r) for(int i = l; i <= r; ++i)
int T, n, a[N], dp[N][N][2];
int read(){
char c; int x = 0, f = 1;
c = getchar();
while(c > '9' || c < '0'){ if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}
int main(){
T = read();
while(T--){
n = read();
rep(i, 1, n) a[i] = read();
rep(i, 1, n) rep(j, 0, min(i, n / 2)) dp[i][j][0] = dp[i][j][1] = inf;
dp[1][1][0] = dp[1][0][1] = -1;
rep(i, 2, n) rep(j, 0, min(i, n / 2)){
if(a[i] > a[i - 1] && j >= 1) dp[i][j][0] = dp[i - 1][j - 1][0];
if(j >= 1 && a[i] > dp[i - 1][j - 1][1]) dp[i][j][0] = min(dp[i][j][0], a[i - 1]);
if(a[i] > a[i - 1]) dp[i][j][1] = dp[i - 1][j][1];
if(a[i] > dp[i - 1][j][0]) dp[i][j][1] = min(dp[i][j][1], a[i - 1]);
}
if(dp[n][n / 2][0] != inf || dp[n][n / 2][1] != inf) puts("Yes!");
else puts("No!");
}
return 0;
}