这题的做法其实和以前大佬的很像,但是第二个那个交换序列的转移我太弱了真的想不到,所以在此提出新的转移方式
状态定义:$f[len][i]$ 表示处理到第 $i$ 个数 $a[i]$ ,将其放入 $U$ 集合中,则此时 $V$ 集合内最后的元素的最小值。
在此并不讨论把 $a[i]$ 放入的是 $U$ 还是 $V$ 集合,因为放不放入元素的是相对的,就是这个集合放入则那个集合不放入,反之亦然。因此将 $U$ 和 $V$ 对调也是没问题的
转移:
分两种情况:
①在 $f[len-1][i-1]$ 的基础上,将 $a[i]$ 直接加入集合,要求 $a[i]>a[i-1]$,这个跟前面的大佬是一样的
② $f[len][i] = a[i-1]$ ,其实就是选取 $U$ 集合的上一个元素 $a[k]$ $0≤k≤i-1$),然后把 $a[k+1]$ 到 $a[i-1]$ 全部加入 $V$ 中,然后把 $a[i]$ 加入 $U$ 中,跟在 $a[k]$ 后面,要求
1) $a[k]<a[i]$
2) $a[k+1]$ 到 $a[i-1]$ 是严格单调递增的
3) $a[k+1]>f[len-1][k]$,因为要把 $a[k+1]$ 接到 $f[len-1][k]$ 后面,就是放到 $V$ 后面
实现:
$sij[i]$ 表示 $a$ 中的 $k$ 项到 $i-1$ 项满足严格单调递增时 $k$ 的最小值,即 $a[sij[i]]$ 到 $a[i-1]$ 严格单调递增
不难发现对于 $a[i]$ 的转移一定有 $sij[i]-1≤k≤i-1$
$t[i]$ 表示 $a[sij[i]-1]$ 到 $a[i-1]$ 中满足 $a[j]<a[i]$ 时 $j$ 的最大值
不难发现其实 $a[sij[i]]$ 到 $a[t[i]]$ 都是小于 $a[i]$ 的,当然如果 $sij[i]>t[i]$ 那就无法转移了
那么第二种转移的条件就转换成:
对于长度为 $len$ 的每个 $i$ 在指定区间内是否存在$a[k+1]>f[len-1][k]$
若 $a[sij[i]-1]<a[i]$,则这个指定区间是 $[sij[i]-1,t[i]]$
否则这个指定区间是 $[sij[i],t[i]]$
是否存在 $a[k+1]>f[len-1][k]$ 这一条件其实与 $f[len]$ 这一维无关而与上一维 $f[len-1]$ 有关,因此在转移 $f[len]$ 时,哪些 $a[k+1]>f[len-1][k]$ 的条件已经存在了并且定了下来,因此可以在每一维转移前先处理出来,而询问区间的话只需要处理出前缀和就行了
因为第一项不好搞,就把 $f[][1]$ 先处理出来,而只有 $f[0][1]$ 和 $f[1][1]$ 是合法的,后面 $len$ 的都是非法的,所以在 dp 转移的时候就不用转移 $i=1$ 的情况了
(代码如下)
代码:
#include <bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||'9'<ch)f^=(ch=='-'),ch=getchar();
while('0'<=ch&&ch<='9')x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return f?x:-x;
}
const int N=2005;
int f[N][N],n,m,a[N],sij[N],t[N],s[N],ok[N];
//f[j][i]:处理到a[i],是U集合中第j个时,V集合最后一个元素的最小值
void solve(){
n=read();
for(int i=1;i<=n;i++){
a[i]=read();
}
memset(f,0x3f,sizeof(f));
memset(t,-1,sizeof(t));
memset(ok,0,sizeof(ok));
sij[1]=1,f[0][0]=-1e18,f[1][1]=-1e18,f[0][1]=a[1];
//初始化一定不能是0,一定要小于0
a[0]=-1e18;
for(int i=2;i<=n;i++){
sij[i]=1;
for(int j=i-1;j>=1;j--){
if(a[j-1]>=a[j]){
sij[i]=j;
break;
}
}
}
for(int i=1;i<=n;i++){
for(int j=sij[i]-1;j<=i-1;j++){
if(a[j]<a[i])t[i]=j;
}
}
for(int i=2;i<=n;i++){
if(sij[i]==1){
if(a[i]>a[i-1])f[0][i]=a[i];
}
else break;
}
for(int len=1;len<=n/2;len++){
for(int k=0;k<=n;k++){
if(a[k+1]>f[len-1][k])ok[k]=1;
s[k]=s[max(0LL,k-1)]+ok[k];
}
//ok[i]就是之前a[i+1]>f[len-1][i]是否成立
for(int i=2;i<=n;i++){
if(a[i]>a[i-1])f[len][i]=f[len-1][i-1];
int down;
if(a[i]>a[sij[i]-1])down=sij[i]-1;
else down=sij[i];
//确定定区间[down,t[i]]
if(t[i]>=down){
if((down==0)?(s[t[i]]>0):(s[t[i]]-s[down-1]>0)){
//s[-1]不合法,要特判一下
f[len][i]=min(f[len][i],a[i-1]);
}
}
}
}
if(f[n/2][n]!=f[n][1])printf("Yes!\n"); //用来判断是否为0x3f
else printf("No!\n");
}
signed main(){
int T=read();
while(T--)solve();
}