文章目录
- 提高课题解
- 一、拦截导弹
- 二、导弹防御系统
- 三、最长公共上升子序列
- 四、二分函数速写
- 基础课题解
- 五、最长上升子序列 II
提高课题解
一、拦截导弹
题目链接
第一问非常简单,直接用之前最长上身子序列模板就行
第二问就有难度了,我们要用最少的递减子序列覆盖这个数组,怎么样才最少呢?把i这个位置放在在i位置之前的所有非递增子序列中的最后一个数大于等于第i个位置的数的非递增子序列,同时我们要放在所有大于等于i位置数的最小那一个的后面,这样才能保证i后面状态的位置能够有更多机会加入到前面的非递增子序列中,如果i这个状态都大于前面所有非递增子序列的最后一个数,那么我们要新开辟一个空间给i状态,因为它不满住前面所有的状态
图解:
在所有最后一个数大于等于i这个位置数的非递增子序列中,i这个位置的数一定要放在这些非递增子序列中最后一个数中最小的那个数?(这里要用贪心证明,证明见acwing彩色铅笔,我这里用个通俗的例子来说明)
见图:
二、导弹防御系统
题目链接
这个题也是经典的dp思想,运用dfs然后剪枝,你的第i个位置的状态要么放到递增子序列里面,要么放在递减子序列里面。
图解:为什么要恢复现场?
这里ans每次有一个结果出现也就是t == n的时候,每次都会更新一下ans(除非比ans小才会更新),ans维护的是所有状态的值,所以我们可以得到最小值
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 55;
int n;
int q[N];
int up[N], down[N];
int ans;
void dfs(int u,int d,int t)
{
if(u + d >= ans) return;//如果某一个方案的u + d
//大于等于之前已有方案的总个数
//,那么把这个方案pass掉
if(t == n)
{
if(ans > u + d) ans = u + d;//每次有比原有的最小方案小的方案,那么更新一下ans
return;//更新完就返回
}
//两种策略,你这个数要么放在递增子序列里面,
//要么放在递减子序列里面,这样就考虑比较全,不会漏掉某一个方案
//如果你放在递增序列里面,这里默认up是递增子序列,因为每次你要加入新数的时候,
//你要找所有小于新数的的数中最大的一个数,
//这样能保证你后面新加的数有更多的机会放入up序列中
//假设你放入的的是小于新数中最小的那一个数,
//那么后面来了一个新数,比原来前面的新数把所有小于新数的的数中最小的一个数大
//但是没有你倒数第二大的数小,那么你又重新开一个序列(这不是纯纯浪费了吗?)
int i;
for(i = 1;i <= u;i ++)
if(up[i] < q[t]) break;
int tmp = up[i];//记录原来的值,我们需要剪枝
up[i] = q[t];
dfs(max(u, i), d, t + 1);
//恢复现场
up[i] = tmp;
int j;
//这里我们要找的递减子序列,那么找的一定是所有递减子序列中最后一个数中,
//大于加入的新数,
//而且还要找递减子序列中大于新数的最小那个那个数,
//这样才能让后面的数有更多的机会放入到递减子序列中
//down默认是一个单剪序列
for(j = 1;j <= d;j ++)
if(down[j] > q[t]) break;
int temp = down[j];
down[j] = q[t];
dfs(u, max(d, j), t + 1);
//恢复现场
down[j] = temp;
}
int main()
{
while(cin >> n && n)
{
ans = 100;
for(int i = 0;i < n;i ++)
cin >> q[i];
dfs(0, 0, 0);
printf("%d\n", ans);
}
return 0;
}
三、最长公共上升子序列
题目链接
贴一下y总的题解
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 3010;
int n;
int f[N][N];
int a[N], b[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];
//我们以b[j]元素为研究对象
//a[i]和b[j]是否能够匹配上
for(int i = 1;i <= n;i ++)
{
int rmax = 1;
for(int j = 1;j <= n;j ++)
{
f[i][j] = f[i - 1][j];
if(a[i] == b[j]) f[i][j] = max(f[i][j], rmax);
if(a[i] > b[j]) rmax = max(rmax, f[i - 1][j] + 1);
}
}
int res = 0;
for(int i = 1;i <= n;i ++) res = max(res, f[n][i]);
cout << res;
return 0;
}
四、二分函数速写
基础课题解
五、最长上升子序列 II
题目链接
这个题有时间限制时间复杂度必须满足nlogn
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int n;
int a[N];
int q[N];
int main()
{
scanf("%d", &n);
for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);
int len = 0;
for (int i = 0; i < n; i ++ )
{
int l = 0, r = len;
while (l < r)
{
int mid = l + r + 1 >> 1;
if (q[mid] < a[i]) l = mid;
else r = mid - 1;
}
len = max(len, r + 1);
q[r + 1] = a[i];
}
printf("%d\n", len);
return 0;
}