文章目录
0. 前言
强相关:
[线性dp] 最长上升子序列(模板题+最长上升子序列模型) 贪心解法
1. LIS +贪心+dfs
重点: 线性 dp
、LIS 问题、贪心
思路:
- [线性dp] 拦截导弹(最长上升子序列模型+贪心)是高度只能不断下降。本题高度可以不断上升和不断下降,但需要维护其单调性。即,最少使用多少个上升、下降子序列将整个序列覆盖掉。
- [线性dp] 拦截导弹(最长上升子序列模型+贪心)我们只需要从前往后考虑每一枚导弹,采用贪心的思路,每次就只有在上升子序列中对两种情况进行决策。而本题就有两种情况,需要先划分该导弹是上升子序列还是下降子序列
- 本题没什么好的方法,只能通过暴力枚举当前导弹放到上升、下降的子序列中的所有情况取最优值即可
dfs
暴搜一般是 2 n 2^n 2n 的计算量- 本题可抽象为
dfs
求最小步数问题,通常有两种解决方式- 记全局最小值
- 迭代加深
dfs
为什么较于bfs
更加常用?bfs
空间容易爆炸,因为需要将这一层的状态都存下来,空间为指数级别dfs
递归时只存储路径,空间为线性级别dfs
可以剪枝,bfs
很难剪枝
[线性dp] 最长上升子序列(模板题+最长上升子序列模型) 和 LIS
贪心+二分的思路强相关。
这里 n=55, 2^55
这个计算量为什么不会超时呢
- 首先答案很小,也就是需要用到的单调序列的个数很少,因此
dfs()
中的depth
这个参数就很小,那么if (su + sd > depth) return false;
这个剪枝就会剪掉很多分支了。
dfs全局最小值代码:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 55;
int n;
int q[N];
int up[N], down[N];
int ans;
// u:当前枚举到那个数,su:最小上升子序列个数,sd最小下降子序列个数
void dfs(int u, int su, int sd) {
if (su + sd >= ans) return ; // su和sd不会再将答案变小,故剪枝,以此找到最小值
if (u == n) {
ans = su + sd; // 枚举结束,直接更新,因为su+sd>n的情况已经提前return了
return ;
}
// 情况1:将当前数放到下降子序列中
int k = 0;
while (k < su && up[k] >= q[u]) k ++;
int t = up[k]; // 现场备份,方便回溯时恢复现场
up[k] = q[u];
if (k < su) dfs(u + 1, su, sd);
else dfs(u + 1, su + 1, sd);
up[k] = t; // 恢复现场
// 情况2:将当前数放到上升子序列中
k = 0;
while (k < sd && down[k] <= q[u]) k ++;
t = down[k];
down[k] = q[u];
if (k < sd) dfs(u + 1, su, sd);
else dfs(u + 1, su, sd + 1);
down[k] = t; // 恢复现场
}
int main() {
while (cin >>n, n) {
for (int i = 0; i < n; ++i) cin >> q[i];
ans = n; // 最坏情况每个数单独一个序列,则最大为n
dfs(0, 0, 0);
cout << ans << endl;
}
return 0;
}
贴一份y总的dfs迭代加深代码:
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 60;
int n;
int h[N];
int up[N], down[N];
bool dfs(int depth, int u, int su, int sd)
{
// 如果上升序列个数 + 下降序列个数 > 总个数是上限,则回溯
if (su + sd > depth) return false;
if (u == n) return true;
// 枚举放到上升子序列中的情况
bool flag = false;
for (int i = 1; i <= su; i ++ )
if (up[i] < h[u])
{
int t = up[i];
up[i] = h[u];
if (dfs(depth, u + 1, su, sd)) return true;
up[i] = t;
flag = true;
break; // 注意由上述证明的贪心原理,只要找到第一个可以放的序列,就可以结束循环了
}
if (!flag) // 如果不能放到任意一个序列后面,则单开一个新的序列
{
up[su + 1] = h[u];
if (dfs(depth, u + 1, su + 1, sd)) return true;
}
// 枚举放到下降子序列中的情况
flag = false;
for (int i = 1; i <= sd; i ++ )
if (down[i] > h[u])
{
int t = down[i];
down[i] = h[u];
if (dfs(depth, u + 1, su, sd)) return true;
down[i] = t;
flag = true;
break; // 注意由上述证明的贪心原理,只要找到第一个可以放的序列,就可以结束循环了
}
if (!flag) // 如果不能放到任意一个序列后面,则单开一个新的序列
{
down[sd + 1] = h[u];
if (dfs(depth, u + 1, su, sd + 1)) return true;
}
return false;
}
int main()
{
while (cin >> n, n)
{
for (int i = 0; i < n; i ++ ) cin >> h[i];
int depth = 0;
while (!dfs(depth, 0, 0, 0)) depth ++ ; // 迭代加深搜索
cout << depth << endl;
}
return 0;
}