2022.12.26动态规划练习

洛谷P1091 [NOIP2004 提高组] 合唱队形

题目描述

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

合唱队形是指这样的一种队形:设 \(k\) 位同学从左到右依次编号为 \(1,2,\)\(,k\),他们的身高分别为 \(t_1,t_2,\)\(,t_k\),则他们的身高满足 \(t_1< \cdots <t_i>t_{i+1}>\)\(>t_k(1\le i\le k)\)

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

输入格式

共二行。

第一行是一个整数 \(n\)\(2\le n\le100\)),表示同学的总数。

第二行有 \(n\) 个整数,用空格分隔,第 \(i\) 个整数 \(t_i\)\(130\le t_i\le230\))是第 \(i\) 位同学的身高(厘米)。

输出格式

一个整数,最少需要几位同学出列。

样例 #1

样例输入 #1

8
186 186 150 200 160 130 197 220

样例输出 #1

4

提示

对于 \(50\%\) 的数据,保证有 \(n \le 20\)

对于全部的数据,保证有 \(n \le 100\)

思路

思路很简单。就是选择出最多的人构成合唱团,且是山峰型的。
状态表示:\(l[i]\)\(r[i]\) 分别表示从 \(1-i\) 的上升子序列和 \(i-n\) 的下降子序列。那么我们需要求这两个的最大值,那么最大的 \(l[i]+r[i]-1\) 即是最多的人,\(n-(l[i]+r[i]-1)\) 即是最少去除的人。
状态计算:状态计算思路同最长上升子序列,考虑 \(l[i]\),即以 \(t[i]\) 为结尾的上升子序列,那么我们考虑它的前一个元素 \(t[j],1≤j<i\),则如果 \(t[i]>t[j]\)\(l[i]=max(l[i], l[j]+1)\)\(r[i]\) 的计算类似。

C++代码

#include <bits/stdc++.h>
using namespace std;
const int N = 110, MOD = 1e6 + 7;
typedef pair<int, int> PII;
typedef long long LL;

int n;
int t[N];
int l[N], r[N]; // l[i]表示从1-i中升序,r[i]表示i-n的降序

int main ()
{
	scanf("%d", &n);
	for (int i = 1; i <= n; i ++)
	{
		scanf("%d", &t[i]);
		l[i] = r[i] = 1;
	}
	for (int i = 1; i <= n; i ++)
		for (int j = 1; j < i; j ++)
			if (t[i] > t[j]) 
				l[i] = max(l[i], l[j] + 1);
	for (int i = n; i >= 1; i --)
		for (int j = n; j > i; j --)
			if (t[j] < t[i])
				r[i] = max(r[i], r[j] + 1);
	int res = 0;
	for (int i = 1; i <= n; i ++)
		res = max(l[i] + r[i] - 1, res);
	cout << n - res << endl;
    return 0;
}

P1233 木棍加工

题目描述

一堆木头棍子共有 \(n\) 根,每根棍子的长度和宽度都是已知的。棍子可以被一台机器一个接一个地加工。机器处理一根棍子之前需要准备时间。准备时间是这样定义的:

第一根棍子的准备时间为 \(1\) 分钟;

如果刚处理完长度为 \(L\),宽度为 \(W\) 的棍子,那么如果下一个棍子长度为 \(L_i\),宽度为 \(W_i\),并且满足 \(L≥L_i,W≥W_i\),这个棍子就不需要准备时间,否则需要 \(1\) 分钟的准备时间;

计算处理完 \(n\) 根棍子所需要的最短准备时间。比如,你有 \(5\) 根棍子,长度和宽度分别为 \((4, 9)\)\((5, 2)\)\((2, 1)\)\((3, 5)\)\((1, 4)\),最短准备时间为 \(2\)(按 \((4, 9)\)\((3, 5)\)\((1, 4)\)\((5, 2)\)\((2, 1)\) 的次序进行加工)。

输入格式

第一行是一个整数 \(n(n≤5000)\),第 \(2\) 行是 \(2n\) 个整数,分别是 \(L_1,W_1,L_2,w_2,…,L_n,W_n\)\(L\)\(W\) 的值均不超过 \(10000\),相邻两数之间用空格分开。

输出格式

仅一行,一个整数,所需要的最短准备时间。

样例 #1

样例输入 #1

5
4 9 5 2 2 1 3 5 1 4

样例输出 #1

2

思路

首先按照长度和宽度从小到大排序,这是贪心,那么就一定有 \(L[i]≤L[i+1]\),这里我们是先比较长度。那么我们就只需要考虑宽度即可。根据 \(dilworth\) 定理,链的最少划分数=反链的最长长度,那么一个序列最少可以分成 \(n\) 个最长不下降子序列,这个序列最长下降子序列长度为 \(m\),则 \(n=m\)

C++代码

#include <bits/stdc++.h>
using namespace std;
const int N = 5010, MOD = 1e6 + 7;
typedef pair<int, int> PII;
typedef long long LL;

int n;
struct Q 
{
	int l, w;
} q[N];

bool cmp(Q x, Q y) 
{
	if (x.l != y.l) return x.l < y.l; // 按长度排序
	return x.w < y.w;
}
int f[N];

int main ()
{
	scanf("%d", &n);
	for (int i = 1; i <= n; i ++)
		scanf("%d%d", &q[i].l, &q[i].w);
	sort(q + 1, q + n + 1, cmp);
	for (int i = 2; i <= n; i ++)
		for (int j = 1; j < i; j ++)
			if (q[j].w > q[i].w) 
				f[i] = max(f[j] + 1, f[i]);
	int ans = 0;
	for (int i = 1; i <= n; i ++)
		ans = max(ans, f[i]);
	cout << ans + 1 << endl;
    return 0;
}

参考

如何使用Dilworth定理

posted @ 2022-12-27 21:25  Cocoicobird  阅读(45)  评论(0)    收藏  举报