动 态 规划

动态规划

最长不下降子序列

[P1020 NOIP1999 普及组] 导弹拦截

题目描述

某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。

输入导弹依次飞来的高度(雷达给出的高度数据是$ \le 50000$的正整数),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

输入格式

1 1 1行,若干个整数(个数$ \le 100000$)

NOIP 原题数据规模不超过 2000。

输出格式

2 2 2行,每行一个整数,第一个数字表示这套系统最多能拦截多少导弹,第二个数字表示如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

样例 #1
样例输入 #1
389 207 155 300 299 170 158 65
样例输出 #1
6
2
提示

为了让大家更好地测试n方算法,本题开启spj,n方100分,nlogn200分

每点两问,按问给分

感觉真的忘得差不多了。

#include<iostream>
using namespace std;

#define N 1000010

int n, num, a[N], f[N], len1, len2;

int main()
{
	while(cin >> num)
		a[++n] = num;
	for(int i = 1; i <= n; i ++)
	{
		f[i] = 1;
		for(int j = 1; j < i; j ++)
		{
			if(a[j] >= a[i] && f[j] + 1 > f[i])
				f[i] = f[j] + 1;
			len1 = max(len1, f[i]);
		}
	}
	int f1[N];
	for(int i = 1; i <= n; i ++)
	{
		f1[i] = 1;
		for(int j = 1; j < i; j ++)
		{
			if(a[j] < a[i] && f1[j] + 1 > f1[i])
				f1[i] = f1[j] + 1;
			len2 = max(len2, f1[i]);
		}
	}
	cout << len1 << endl << len2 << endl;
	return 0;
}

简析

动态规划,就我现在的理解就是:把一个问题分为若干个子问题,保证每个问题的解决方式相同或相似。也就是阶段与状态的关系,这个阶段解决出来的问题,必须是能够保证让,后续的解决过程,及结果最优。

就例如这个问题,可以看做是求 i 前面的比自己小的最长单调序列。判断条件是if(a[j] < a[i] && f1[j] + 1 > f1[i]),循环一遍 i 之前的数字,因为f [ j ] ,相当于是之前判断过的,所以能保证它记录的是他之前的比自己小的最长单调序列。这就有一种递归的思想。

最长上升子序列的问题解决了,我又遇到了最长不下降子序列的问题。

    for(int i = n - 1; i >= 1; i --)
    {
        for(int j = i + 1; j <= n; j ++)
        {
            if(a[j] < a[i] && smaller[j] + 1 >= smaller[i])
                smaller[i] = smaller[j] + 1;
        }
    }
 for(int i = 1; i <= n; i ++)
    {
        smaller[i] = 1;
        for(int j = 1; j < i; j ++)
        {
            if(a[j] > a[i] && smaller[j] + 1 >= smaller[i])
                smaller[i] = smaller[j] + 1;
        }
    }

正着求只有60分。找了组数据:

输入
100
157 165 138 130 141 206 160 164 216 216 145 227 180 147 170 216 154 144 171 230 205 137 169 181 146 133 220 138 175 207 173 155 136 167 144 166 140 191 145 162 214 213 151 200 166 131 221 154 161 229 136 194 215 202 137 202 157 132 166 215 218 230 168 217 131 189 203 131 207 176 172 211 187 158 165 156 179 194 200 145 130 183 174 143 148 218 213 187 204 221 160 169 168 224 163 132 226 135 201 217
输出
78
正着时的smaller
1 1 2 3 2 1 2 2 1 1 3 1 2 3 3 2 4 5 3 1 3 6 4 4 5 7 2 6 5 3 6 7 8 7 8 8 9 4 9 9 3 4 10 5 8 11 2 10 10 2 11 6 3 5 11 5 11 12 8 3 3 1 7 4 13 7 5 13 5 8 9 5 8 11 10 12 9 6 6 13 14 9 10 14 13 3 5 8 6 3 11 11 12 3 13 15 3 15 7 4
77
逆着
9 10 6 1 6 14 9 9 14 14 7 15 12 8 11 14 8 6 11 14 13 5 10 12 7 4 13 5 11 11 10 7 4 9 6 8 5 9 5 7 12 11 5 10 7 2 11 5 6 11 4 9 10 9 4 9 5 3 6 9 10 10 6 9 2 8 8 2 8 7 6 8 7 5 5 4 6 7 7 3 1 6 5 2 2 7 6 5 5 5 2 4 3 3 2 1 2 1 1 1
78

写了一堆的我终于意识到了:正着求的是左边比自己小的,逆着是求右边比自己小的 -_- 。对比一下数据就能发现……

有时候想明白以后,总是觉得想明白以前的自己好脑瘫。

哦对了,上面求右边比自己小的序列的题是合唱队形,就是要求队列中能使先单调上升,再单调下降的最长序列。思路就是把每个点左边比自己小的和右边比自己小的(这里都指最长单调序列)给加起来,找最大值。

[P1091 NOIP2004 提高组] 合唱队形

最长公共子序列

记它的代码是很简单的,但是过不了多久就会忘掉。

我们先来看一下子序列的定义:一个序列S任意删除若干个字符得到新序列T,那么T叫做S的子序列。所以不能想当然认为它是连续的才行。问题就是求两个序列最长相同子序列的长度(非连续)。而子串就是连续的了。

P1439 【模板】最长公共子序列

代码

#include<iostream>
using namespace std;

#define N 1010

int n;
int a[N], b[N], ans[N][N];

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i ++)
    {
        cin >> a[i];
    }
    for(int i = 1; i <= n; i ++)
    {
        cin >> b[i];
    }
    for(int i = 1; i <= n; i ++)
    {
        for(int j = 1; j <= n; j ++)
        {
            ans[i][j] = max(ans[i - 1][j], ans[i][j - 1]);
            if(a[i] == b[i])
                ans[i][j] = max(ans[i - 1][j - 1] + 1, ans[i][j]);
        }
    }
    cout << ans[n][n] << endl;
    return 0;
}

分析

依旧是按照我那个有点玄学的思路:假设前面的都已经求好了。这样的话,如果有一对数字相同,那么这时最大长度就必然是前面最长长度 + 1,也就是ans{i - 1}{j - 1} + 1,当然,要注意一下自身之前可能操作过,要求max。当数字不相等的话,此时的最大值必定来自ans{i - 1}{j}或ans{i}{j - 1},为什么捏?令 z = max( a n a_n an, b m b_m bm),则 z != a n a_n an与z != b m b_m bm必定满足一项,假设z != b m b_m bm,z = a n a_n an,z 就是 b m − 1 b_{m-1} bm1 a n a_n an得到的。反之亦然。所以最大值是从这两个之间得出的。

2022-6-3 21:24:37

背包问题

01背包

采药问题就是一个经典题型,就是关于要不要取的问题。

[P1048 NOIP2005 普及组] 采药

题目描述

辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”

如果你是辰辰,你能完成这个任务吗?

输入格式

第一行有 2 2 2 个整数 T T T 1 ≤ T ≤ 1000 1 \le T \le 1000 1T1000)和 M M M 1 ≤ M ≤ 100 1 \le M \le 100 1M100),用一个空格隔开, T T T 代表总共能够用来采药的时间, M M M 代表山洞里的草药的数目。

接下来的 M M M 行每行包括两个在 1 1 1 100 100 100 之间(包括 1 1 1 100 100 100)的整数,分别表示采摘某株草药的时间和这株草药的价值。

输出格式

输出在规定的时间内可以采到的草药的最大总价值。

样例 #1
样例输入 #1
70 3
71 100
69 1
1 2
样例输出 #1
3
提示

【数据范围】

  • 对于 30 % 30\% 30% 的数据, M ≤ 10 M \le 10 M10
  • 对于全部的数据, M ≤ 100 M \le 100 M100

【题目来源】

NOIP 2005 普及组第三题

边看代码跟容易理解
#include<iostream>
using namespace std;

#define N 1010

int T, n, t[N], w[N], ans[N];

int main()
{
	cin >> T >> n;
	for(int i = 1; i <= n; i ++)
		cin >> t[i] >> w[i];
	for(int i = 1; i <= n; i ++)
	{
		for(int j = T; j >= t[i]; j--)
		{
			ans[j] = max(ans[j], ans[j - t[i]] + w[i]);
		}
	}
	
	cout << ans[T];
	
	return 0;
}
简析

直接看 j 这个循环,为了防止越界,从 T(容量) 开始,向t[ i ],也就是这个药的体积递减,那么为什么要从后面开始向前遍历呢?

我们先放着这个问题,先考虑如何求最优方案。ans[ i ],这里的 i 表示这个容量下的最优放法。那么ans[ j - t[ i ] ],就是去掉这个药的情况下的最优解,这是我们之前已经求出来了的。dp我感觉有时候不用考虑那么多,只要先考虑普遍的情况,假设前面的已经求完了,再去想后面的怎么解决就好了。max( ans[ j ], ans[ j - t [ i ] ] + w[ i ] ) 那就是考虑加上 i 药品价值高还是不加高。前面说了ans[ j - t[ i ] ] 是不加情况下的最优解,那再加 i 肯定是加上 i 的最优解,与ans[ j ]比较,得出答案。(注意这里的ans[ j ]可以是前面求过的)

这时候再去考虑为什么倒序:假如是正序,ans[ j - t [ i ] ]有可能已经被改变过了,就有可能不是只放一遍。可以看着代码再考虑一遍。

完全背包

因为完全背包可以选择无数个东西,所以思路有很大不同。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值