传送门:https://www.luogu.com.cn/problem/P4728
题目描述
这题题面跟数据有点出入,所求并非单调上升序列,而是不下降序列,如 \({2, 3, 3}\) 是合法的
输入格式
输出格式
输出 \(m\) 行, 若第 \(i\) 行为“好序列”,输出 \(Yes!\),否则输出 \(No!\)。
样例
样例就不给了,大家自己看原题吧。(逃)
讲解
这题相当邪乎。。。
一开始看到这题,同班的大佬们一直认为就是一道 \(Dilworth\) 定理的水题,于是一发LIS,都不用带优化的就过了。。。
然而这真的对吗,并不对。。。虽然数据能过,但是他的正确性是无法证明的,事实上有位大佬给出了hack数据,但是我并没看到。。。
所以说去网上看正解,但是死活理解不了,比如洛谷里大佬的那篇题解,光是那堆初始化就看的我一脸懵。代码也不带打个注释的(明明自己菜还要喷别人)
所以,还是要靠自(da)己(lao)啊(感恩wxz)。
从暴力到优化的思路大家可以参考那篇题解,这里只介绍一下正解(我以为的)。
我们重新定义一下 \(f\) 数组:
\(f[i][j]=k\) 表示,前 \(i\) 个数中,含有 \(a[i]\) 的一个集合长度为 \(j\),不含 \(a[i]\) 的那个集合的最后一位的最小值,即 \(k\) 。
可能有点绕,但大家一定要好好理解一下,因为这道题完全就是在吃定义,如果无法理解定义的话是没办法做的。
我们简单的认为含 \(a[i]\) 的那个集合为第一个集合,另一个为第二个集合。
理解之后我们来考虑如何转移。
我这里采用了刷表法,大家可以自己YY填表法。。。
我们枚举 \(i\),对于每个 \(i\),我们考虑 \(i+1\),选择无非只有两种,一:把 \(a[i+1]\) 接到含有 \(a[i]\) 集合后面,二:把 \(a[i+1]\) 接到另一个集合后面。
两个集合没有本质区别,所以第二个转移有一个很神奇的交换两个集合的操作,大家注意一下。
第一个转移:
把 \(a[i+1]\) 接到含有 \(a[i]\) 的那个集合后面,此时要满足 \(a[i+1] >= a[i]\)
转移方程为:\(f[i+1][j+1]=min(f[i+1][j+1],f[i][j])\)
为什么这么些呢,还是吃定义:我们维护的值是第二个集合的最后一位的最小值,我们在第一个集合后面接东西与他何关?所以直接取min
第二个转移:
把 \(a[i+1]\) 接到不含 \(a[i]\) 的那个集合后面,此时要满足 \(a[i+1] >= f[i][j]\)
转移方程为:\(f[i+1][i-j+1]=min(f[i+1][i-j+1],a[i])\)
我想大家可能对这个式子很迷惑,这都什么跟什么啊,我的 \(f[i][j]\) 去哪里了qwq。
我们把 \(a[i+1]\) 接到不含 \(a[i]\) 的那个集合也就是第二个集合的后面,那么此时,第二个集合变成了对于 \(i+1\) 而言的第一个集合,因为此时第二个集合最后一位是 \(a[i+1]\) ,符合我们的第一个集合的定义,他的长度是 \(i-j+1\),所以我们要更新 \(f[i+1][i-j+1]\),为什么要用 \(a[i]\) 来更新呢,因为此时原来的第二个集合变成了第一个集合,自然原来的第一个集合(结尾为 \(a[i]\))就变成了现在的第二个集合。
然后这道题就基本结束了,要注意的是初始化 \(f[1][1]=-1\),因为数据里可能有0,转移时枚举 \(j\) 从 1 到 \(i\)。
最后判断 \(f[n][n/2]\) 是否被更新即可。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
const int maxn = 2000 + 10;
inline int read(){
int s = 0, w = 1;
char c = getchar();
while(c < '0' || c > '9'){if(c == '-') w = -1; c = getchar();}
while(c >= '0' && c <= '9') s = s * 10 + c - '0', c = getchar();
return s * w;
}
int n, m;
int a[maxn];
int f[maxn][maxn];
int main(){
m = read();
while(m--){
n = read();
for(int i = 1; i <= n; i++)
a[i] = read();
memset(f, 0x3f, sizeof f);
f[1][1] = -1;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= i; j++){
if(a[i + 1] >= a[i])
f[i + 1][j + 1] = std:: min(f[i + 1][j + 1], f[i][j]);
if(a[i + 1] >= f[i][j])
f[i + 1][i - j + 1] = std:: min(f[i + 1][i - j + 1], a[i]);
}
}
if(f[n][n / 2] > 1e7) printf("No!\n");
else printf("Yes!\n");
}
return 0;
}