传送门:https://www.luogu.com.cn/problem/P4728
题目描述
这题题面跟数据有点出入,所求并非单调上升序列,而是不下降序列,如 ({2, 3, 3}) 是合法的
输入格式
输出格式
输出 (m) 行, 若第 (i) 行为“好序列”,输出 (Yes!),否则输出 (No!)。
样例
样例就不给了,大家自己看原题吧。(逃)
讲解
这题相当邪乎。。。
一开始看到这题,同班的大佬们一直认为就是一道 (Dilworth) 定理的水题,于是一发LIS,都不用带优化的就过了。。。
然而这真的对吗,并不对。。。虽然数据能过,但是他的正确性是无法证明的,事实上有位大佬给出了hack数据,但是我并没看到。。。
所以说去网上看正解,但是死活理解不了,比如洛谷里大佬的那篇题解,光是那堆初始化就看的我一脸懵。代码也不带打个注释的(明明自己菜还要喷别人)
所以,还是要靠自(da)己(lao)啊(感恩wzx)。
从暴力到优化的思路大家可以参考那篇题解,这里只介绍一下正解(我以为的)。
我们重新定义一下 (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!
");
else printf("Yes!
");
}
return 0;
}