洛谷P4728 [HNOI2009]双递增序列

传送门: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;
}

猜你喜欢

转载自www.cnblogs.com/Zfio/p/13364440.html
今日推荐