[AcWing算法提高课笔记]1.2.1、1.2.2最长上升子序列模型

题目框架

在这里插入图片描述

回顾:最长上升子序列

题目链接

在这里插入图片描述
方法二:贪心。设 q [ l e n ] q[len] q[len] 表示的是长度为 l e n len len 的上升子序列中末尾数的最小值, r e s res res 是最终答案。对于每一个数 a i a_i ai,如果它大于当前的LIS的末尾数的最小值,那么把它接到这个LIS后面即可,q[++res]=ai,否则二分出最小的 l e n len len 使得 q [ l e n ] > a i q[len]>a_i q[len]>ai,用 a i a_i ai 去替换 q [ l e n ] q[len] q[len] q[len]=ai,这样使得长度为 l e n len len 的上升子序列的末尾数的最小值更小。时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

//贪心代码
int find(int x){
    
      //二分
    int l = 1, r = len;
    while (l < r){
    
    
        int mid = l + r >> 1;
        if (q[mid] >= x) r = mid;
        else l = mid + 1;
    }
    return l;
}
void solve(){
    
    
	for(int i = 2; i <= n; i ++){
    
    
        if (a[i] > q[len]) q[++ len] = a[i];
        else int l = find(a[i]), q[l] = a[i];
  	}
}

一、怪盗基德的滑翔翼

题目链接

在这里插入图片描述
正向、反向求两次LIS长度,取最大值。

二、登山

题目链接

在这里插入图片描述
对每个点求以它结尾的LIS以及反向LIS长度之和减一,取最大值。

三、合唱队形

题目链接

题目问的是“最少需要几位同学出列”,只要上题登山输出 n − r e s n-res nres 即可。

四、友好城市

题目链接

在这里插入图片描述
所有桥与桥之间不能相交即 ∀ \forall 已经架桥的二元组 ( x , y ) (x,y) (x,y),若 x i < x j x_i<x_j xi<xj,则 y i < y j y_i<y_j yi<yj,否则 ( x i , y i ) (x_i,y_i) (xi,yi) ( x j , y j ) (x_j,y_j) (xj,yj) 相交,其中至少有一组不能架桥。将所有友好城市 ( x , y ) (x,y) (x,y) 按照 x x x( y y y) 从小到大排序,题意即求 y ( x ) y(x) y(x) 上的LIS长度。

五、最大上升子序列和

题目链接

在这里插入图片描述
与LIS状态表示和计算类似,只不过状态表示的是以该点结尾的最大上升子序列,转移的时候加的是数值,不是1。

六、拦截导弹

题目链接

在这里插入图片描述
第一问:最长不上升子序列长度;

第二问:可由贪心求得。由狄尔沃斯定理,即最长上升子序列长度。

第二问贪心证明:要证 A = B A=B A=B,只要证 A ≤ B A\le B AB (显然 A ≥ B A\ge B AB)。
假设最优解与贪心方案不同,找到第一个不同的地方( A A A 中方案为 a a a B B B 中方案为 b b b):

在这里插入图片描述
可知, x ≤ a ≤ b x\le a\le b xab,那么 b b b 后面的序列与 a a a 后面的序列完全可以交换,并且保持最终结果不变。因此,在贪心结果下,最多调整 n n n 次,可以将其变为最优结果,而最终结果不会变得更差,即 A ≥ B A\ge B AB。得证。

//第二问贪心的代码
//cnt为第二问结果
for(int i = 1; i < n; i ++){
    
    
    int k = 0;
    while (k < cnt && g[k] < a[i]) k ++;  //找到结尾大于等于它的最小子序列(g[]数组一定是有序的,找到的第一个就是最小的)
    g[k] = a[i];  //接到子序列后面
    if (k >= cnt) cnt ++;  //重新创建新的子序列
}

七、导弹防御系统

题目链接

这道题并不是DP,是DFS问题。每个导弹有两种情况:放在单调上升序列中、放在单调下降序列中 (怎么放如上题贪心流程),再对每一种情况继续深搜下一个导弹,直到得到答案。

//dfs代码
void dfs (int s, int su, int sd){
    
    
    if (su + sd >= ans) return; //肯定不是最优解,不用再搜索下去
    if (s == n){
    
    
        ans = su + sd;  //更新最优解
        return;
    }
    int k = 0, tmp;
    while (k < su && up[k] >= a[s]) k ++;
    tmp = up[k], up[k] = a[s];  //同上题第二问
    if (k >= su) dfs(s + 1, su + 1, sd);
    else dfs(s + 1, su, sd), up[k] = tmp;

    k = 0;
    while (k < sd && down[k] <= a[s]) k ++;
    tmp = down[k], down[k] = a[s];
    if(k >= sd) dfs(s + 1, su, sd + 1);
    else dfs(s + 1, su, sd), down[k] = tmp;
}

八、最长公共上升子序列

题目链接

在这里插入图片描述
状态表示:设 f [ i , j ] f[i,j] f[i,j] 表示由第一个序列前 i i i 个字母和第二个序列前 j j j 个字母 (LCS),且以 b [ j ] b[j] b[j] 结尾 (LIS) 的最长公共上升子序列的长度。
状态计算:先对 f [ i , j ] f[i,j] f[i,j] 分两种情况:

1. a [ i ] ≠ b [ j ] a[i]\ne b[j] a[i]=b[j],那么 f[i,j]=f[i-1,j]
2. a [ i ] = b [ j ] a[i]=b[j] a[i]=b[j],这时候按照LIS的做法,用 f [ i , 1 ] ~ f [ i , j − 1 ] f[i,1]~f[i,j-1] f[i,1]f[i,j1] 更新 f [ i , j ] f[i,j] f[i,j] 的答案。

//2如下
if (a[i] == b[j])
   for (int k = 1; k < j; k ++)
   	if (b[k] < b[j]) //此时a[i]=b[j]
   		f[i][j] = max(f[i][j], f[i][k] + 1);

最后求 f [ n , 1 ] ~ f [ n , m ] f[n,1]~f[n,m] f[n,1]f[n,m] 的最大值即为答案。时间复杂度 O ( n 3 ) O(n^3) O(n3) 会爆。

考虑优化一重循环:在做LIS过程中,需要枚举所有小于 b [ j ] b[j] b[j] b [ k ] b[k] b[k],而此时 b [ j ] = a [ i ] b[j]=a[i] b[j]=a[i],因此要枚举所有小于 a [ i ] a[i] a[i] b [ k ] b[k] b[k],发现这一循环已经与 j j j 无关了,此时这一循环的含义是找到满足 b [ k ] b[k] b[k] 小于 a [ i ] a[i] a[i] f [ i − 1 , k ] f[i-1,k] f[i1,k] 的最大值。用 m a x v maxv maxv 记录这一最大值,并在循环 j j j 时更新该最大值即可。

//2可变为
if (a[i] == b[j]) f[i][j] = max(f[i][j], maxv);
if (b[j] < a[i]) maxv = max(maxv, f[i][j] + 1);
//省去了k的一重循环

猜你喜欢

转载自blog.csdn.net/f4u4u4r/article/details/121063552