洛谷 P4728 [HNOI2009]双递增序列

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

原文地址:https://www.cnblogs.com/Zfio/p/13364440.html