【模板】LIS模板 洛谷P1091 [NOIP2004提高组]合唱队形 [2017年4月计划 动态规划11]

以题写模板。

写了两个:n^2版本与nlogn版本

P1091 合唱队形

题目描述

N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形。

合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2…,K,他们的身高分别为T1,T2,…,TK, 则他们的身高满足T1<...<Ti>Ti+1>…>TK(1<=i<=K)。

你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。

输入输出格式

输入格式:

输入文件chorus.in的第一行是一个整数N(2<=N<=100),表示同学的总数。第一行有n个整数,用空格分隔,第i个整数Ti(130<=Ti<=230)是第i位同学的身高(厘米)。

输出格式:

输出文件chorus.out包括一行,这一行只包含一个整数,就是最少需要几位同学出列。

输入输出样例

输入样例#1:
8
186 186 150 200 160 130 197 220
输出样例#1:
4

说明

对于50%的数据,保证有n<=20;

对于全部的数据,保证有n<=100。

LIS,即最长上升子序列。

n^2算法:

状态:f[i]表示前i个数(包含第i个)最长上升子序列长度

转移方程:f[i] = max(f[j]),其中1 <= j < i

初始状态:f[i] = 1,其中1 <= i <= n

这个题目很显然正着一遍上升,倒着一遍上升。

正确解法:把所有的点扫一遍,结果为 人数 - (同一个点的正着上升与倒着上升之和减去1(因为加了两次当前这个点)) 的最小值。

错误解法:把所有的点扫一遍,结果为 人数 - (第i个人正着上升与第i+1倒着上升之和))最小值  因为倘若num[i] > num[i+1],可能第i -1个人正着上升与第i个人倒着上升之和才是正解

反例数据(洛谷上的一组):

100
225 176 227 185 147 171 188 204 152 144 210 190 188 140 150 213 178 177 188 217 154 178 226 217 181 171 206 130 165 135 205 142 180 228 160 179 230 208 196 217 225 180 204 137 149 139 158 133 169 168 145 175 161 154 230 222 210 174 130 186 207 169 192 193 194 223 138 136 173 207 180 218 201 183 190 218 176 149 191 156 206 140 213 151 179 219 202 149 182 148 156 156 179 142 212 135  133 183 201 219

(有耐心的同学自己看吧。。反正我知道放了也是白放。。

nlogn的算法:

f[i]表示最长公共子序列长度为i的子序列末尾数的最小值,初始为正无穷大(0x3f3f3f3f)

可知f[i]一定是单调

对于num[i],只需要在f中找出大于他的数中最小的,替换掉,替换的这个数的下标就是第i个数的最长公共子序列长度。

手动模拟效果更佳。

n^2版本:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <vector>
#define max(a,b) ((a) > (b) ? (a) : (b))
#define min(a,b) ((a) < (b) ? (a) : (b))
const int INF = 999999999;
const int MAXN = 100 + 10;
inline int read()
{
	int x = 0;char ch = getchar();char c = ch;
	while(ch > '9' || ch < '0')c = ch,ch = getchar();
	while(ch <= '9' && ch >= '0')x = x * 10 + ch - '0',ch = getchar();
	if(c == '-')return -1 * x;
	return x;
}


int lis[MAXN],lds[MAXN],num[MAXN],n,ans,len;


int main()
{
	n = read();
	for(int i = 1;i <= n;i ++)
	{
		num[i] = read();
	}

	for(int i = 1;i <= n;i ++)
	{
		lis[i] = 1;
		for(int j = 1;j <= i - 1;j ++)
		{
			if(num[j] < num[i])lis[i] = max(lis[i], lis[j] + 1);
		}
	}
	
	for(int i = n;i >= 1;i --)
	{
		lds[i] = 1;
		for(int j = n;j >= i + 1;j --)
		{
			if(num[j] < num[i])lds[i] = max(lds[i], lds[j] + 1);
		}
	}

	ans = INF;
	for(int i = 1;i <= n;i ++)
	{
		ans = min(ans, n - lis[i] - lds[i]);
	}
	printf("%d", ans);
	return 0;
}

 nlogn版本:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <vector>
#define max(a,b) ((a) > (b) ? (a) : (b))
#define min(a,b) ((a) < (b) ? (a) : (b))
const int INF = 999999999;
const int MAXN = 100 + 10;
inline int read()
{
	int x = 0;char ch = getchar();char c = ch;
	while(ch > '9' || ch < '0')c = ch,ch = getchar();
	while(ch <= '9' && ch >= '0')x = x * 10 + ch - '0',ch = getchar();
	if(c == '-')return -1 * x;
	return x;
}


int lis[MAXN],lds[MAXN],f[MAXN],num[MAXN],n,ans,len;


int main()
{
	n = read();
	for(int i = 1;i <= n;i ++)
	{
		num[i] = read();
	}
	
	memset(f, 0x3f, sizeof(f));
	f[1] = num[1];lis[1] = 1;
	for(int i = 2;i <= n;i ++)
	{
		f[lis[i] =  std::lower_bound(f + 1, f + 1 + n, num[i]) - f] = num[i];
	}
	
	memset(f, 0x3f, sizeof(f));
	f[1] = num[n];lds[n] = 1;
	for(int i = n - 1;i >= 1;i --)
	{
		int temp = std::lower_bound(f + 1,f + 1 + n, num[i]) - f;
		lds[i] = temp;
		f[temp] = num[i];
	}
	
	ans = INF;
	for(int i = 1;i <= n ;i ++)
	{
		ans = min(ans, n - lis[i] - lds[i] + 1);
	}
	printf("%d", ans);
	return 0;
}
原文地址:https://www.cnblogs.com/huibixiaoxing/p/6740575.html