LIS问题(最长上升子序列)与导弹拦截

本文探讨了最长不下降子序列(LMS)问题,它涉及到寻找序列中的单调子序列。LMS问题可以扩展到偏序集和导弹拦截问题。文中详细解释了动态规划和单调栈的方法来解决LIS问题,并给出了一个导弹拦截的例子,其中Q1要求求出最长不下降子序列,Q2考察了如何使用最少的拦截系统拦截所有导弹。动态规划和贪心策略在这里起到了关键作用。

(前言)最长上升子序列 L I S ( l o n g e s t   i n c r e a s i n g   s u b s e q u e n c e ) LIS(longest\, increasing\, subsequence) LIS(longestincreasingsubsequence)我其实觉得可以拓展名称为最长单调子序列 L M S ( l o n g e s t   m o n o t o n o u s   s u b s e q u e n c e ) LMS(longest \,monotonous \,subsequence) LMS(longestmonotonoussubsequence)。另外 L M S LMS LMS还可以拓展为偏序集问题,详见这位大佬的博客——偏序集,Dilworth定理与导弹拦截

定义

最长单调子序列就是指在一段序列中,可以从左到右不改变左右顺序的选出若干个数,使他们满足某种单调关系,并求出最大值,而最长上升子序列就是其中的一种。

解读与分析

这种满足单调性的问题,我们一般尝试着去找单调的传递性.如LIS我们就利用他的单调,比如:将一段LIS存储到一个数组里最小值一定在数组的最右侧等。又或者是利用单调队列,单调栈这种对单调性有要求的特殊处理方式进行维护和求得所需的值。
然后由于是传递性问题,我们还往往可以选择DP进行转移。
为了和下面的例题契合,我们接下来讨论的是 最长不下降子序列而不是最长上升子序列
f i f_i fi为以 a i a_i ai为结尾的最长不下降子序列的长度
转移方程
f i = m a x j = 1 , a i > a j i − 1 f j + 1 f_i = max_{j=1,a_i > a_j}^{i-1}f_j + 1 fi=maxj=1,ai>aji1fj+1
对于朴素的枚举转移当然就是 O ( n 2 ) O(n^2) O(n2),接下来我们考虑进行优化。我们先尝试用单调栈来处理这个问题:
考虑到单调性:对于末尾为 a k a_k ak的所有上升子序列中,如果长度为 i i i的上升子序列存在,那么长度为 i − 1 i-1 i1的也存在。在满足 j < i , k < i j<i,k<i jiki f i = f k f_i=f_k fi=fk时,如果 a j < a k a_j<a_k ajak显然取 j j j 比取 k k k 优。因此如果记 g i , j g_{i,j} gi,j为考虑前 i i i 个数,长度为 j j j 的上升子序列,末尾最小是多少,那么这个序列是单调不降的(末尾数不可能更小)。
那么我们就可以将这个最长序列看成一个单调的栈(其实不一定是栈) ∀ i < j 满 足 s t [ i ] ≤ s t [ j ] \forall i < j 满足st[i]\leq st[j] i<jst[i]st[j]由此构成了一个从下到上不下降的栈,

所以我们在队列中枚举 i 位置时,如果栈顶的元素比 a i a_i ai 小那我们肯定就将 a i a_i ai加入栈中并且长度++。

但如果栈顶的元素比 a i a_i ai要大的话我们就尝试在栈中找到第一个比 a i a_i ai大的元素(注意是大于,不是大于等于),用 a i a_i ai来代替这个元素这样的话有可能会使栈顶的更新要求更低更容易加入新元素到栈顶。

eg:目前栈:7 8 10 11 要加入的 a i a_i ai = 4,5,6,8

则依次找到第一个大于4的元素7 将4替换为7,第一个大于5的数8将5替换为8…这样栈变为了4 5 6 8 需要加入的数只需要大于等于8而不是原先的11.
总结:我们从左到右进行如下两个操作:

if(a[i] > st[top]) st[++top] = a[i];
if(a[i] < st[top]) {int y = upper_bound(st+1,st+top+1,a[i]) - st;st[y] = a[i];}
//手写upper_bound
int find(int x)
{
    int l = 0,r = top + 1;//不针对于LIS而言,x 可能大于 st[top],故r右边界为top + 1
    int mid = (l + r) >> 1;
    while(l < r)
    {
        if(a[mid] <=  x) l = mid;
        if(a[mid] >  x) r = mid - 1;
        mid = (l + r) >> 1;
    }
    return l;
}

例题一

P1020导弹拦截

题目大意:

给定若干个在空中的导弹的高度,需要你进行拦截。某一个拦截系统的特点是,在拦截一个导弹后便只能拦截高度不高于此次拦截高度的导弹,现有两问:
Q 1 Q1 Q1 只有一个系统时最多总共拦截多少颗导弹
Q 2 Q2 Q2 需要至少多少个系统才能拦截所有导弹。

题目解读与分析

读题目进行一个简单的翻译:
Q 1 Q1 Q1 求出最长不上升子序列 直接套上面的板子即可
Q 2 Q2 Q2 有点不好描述,我们只能尝试硬做:
我们就从头到尾枚举模拟。然后尝试想一下模拟情况可以想到有一个贪心:
假设目前准备摧毁第 i i i个导弹,然后由于模拟我们得到了目前所需的 m m m个导弹系统(目前高度为 h h h),那么在这么多个系统中我们肯定选能满足条件的最低的那一个,这样的状态肯定是最优的(留着小的不如留着大的),然后我们就可以一边枚举一边查找第一个大于等于 h i h_i hi的系统,使这个系统的高度变为 h i h_i hi。当然,如果最高的系统都无法满足条件,就增加一个当前高度的系统到末尾,便构成了一个单调不减的序列,我们查找可以直接用lower_bound。
另外每次更新已有系统的高度时,这个序列仍会保持单调不减性,因为更新的数是从左到右第一个 ≥ h i \ge h_i hi的数,位置为x,说明 s t [ 1... x − 1 ] < h i ≤ s t [ x + 1.. m ] st[1...x-1] < h_i \leq st[x+1..m] st[1...x1]<hist[x+1..m],所以修改 s t [ x ] = h i st[x] = h_i st[x]=hi单调性不变

参考代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 100;
int n,k,h[N],st[N],tot = 1;
int find(int x)//upper_bound 
{
	int l = 0,r = x,mid;//从0开始的upper_bound,因为可能有x > st[1]
	while(l < r)
	{
		mid = (l + r + 1) >> 1;
		if(st[mid] >= h[x]) l = mid;
		else 			   r = mid - 1;
	}
	return l;
}
int f2(int x)//lower_bound
{
	int l = 1,r = tot,mid;
	while(l < r)
	{
		mid = (l + r) >> 1;
		if(st[mid] < h[x]) l = mid + 1;
		else 			    r = mid;
	}
	return l;
}
int Read()
{
	int x = 0,k = 1;char ch = getchar();
	while(!isdigit(ch)) {if(ch == '-') k = -1;ch = getchar();}
	while(isdigit(ch)) {x = (x << 3) + (x << 1) + ch - '0';ch = getchar();}
	return x * k;
}
int main()
{
	while(~scanf("%d",&h[++n])); n--;
	for(int i = 1;i <= n; i++)
	{
		if(i == 1) st[1] = h[1];
		else 
		{
			if(h[i] <= st[tot]) st[++tot] = h[i];
			else {int y = find(i) + 1;;st[y] = h[i];}
		}
	}
	for(int i = 1;i <= n; i++)
		st[i] = 0;
	st[1] = h[1]; 
	cout << tot << '\n';
    tot = 0;
	for(int i = 2;i <= n; i++)
	{	
        if(st[tot] < h[i]) st[++tot] = h[i];
        else{int y =f2(i);st[y] = h[i];}
    }
    cout << tot;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值