最长上升子序列模型DP(LIS)

问题:数组a,长度n,求a的最长子序列长度

状态定义:f[i] 定义为元素a[i]结尾的最长上升子序列长度,答案为max(f),初始化f[0] = 0,f[i] = 1

状态转移:从0 <= j < i的范围内找a[j] < a[i] 的最大f[j] + 1来更新f[i]

f = [1]*n
for i in range(n):
    for j in range(i):
        if a[j] < a[i]:
            f[i] = max(f[i],f[j] + 1)
print(max(f))

LIS除了用DP,还可以用贪心+二分来求

g = []
for x in a:
    idx = bisect_left(g,x)
    if idx == len(g):
        g.append(idx)
    else:
        g[idx] = x
print(len(g))

题目1:怪盗基德的滑翔翼

# 由于可以往两个方向飞,所以要求两个方向的LIS长度取最大
def LIS(a):
    # 以i结尾的最长上升子序列
    f = [1]*n
    for i in range(n):
        for j in range(i):
            if a[j] < a[i]:
                f[i] = max(f[i],f[j] + 1)
    return max(f)

for _ in range(int(input())):
    n = int(input())
    a = list(map(int,input().split()))
    print(max(LIS(a),LIS(a[::-1])))

登山

input()
a = list(map(int,input().split()))
n = len(a)
# 最长上升子序列
f = [1]*n
for i in range(n):
    for j in range(i):
        if a[j] < a[i]:
            f[i] = max(f[i],f[j] + 1)

# 最长下降子序列
g = [1]*n
for i in range(n - 1,-1,-1):
    for j in range(n - 1,i,-1):
        if a[i] > a[j]:
            g[i] = max(g[i],g[j] + 1)

print(max(a + b - 1 for a,b in zip(f,g)))

友好城市

# 这题需要一点思考,如何抽象成LIS问题,要求两岸友好城市连线不相交,我们把一边按照编号从小到大排序,#然后求这些编号对应的友好城市编号的最长上升子序列即可,这样求得的友好城市连线就不会相交了
n = int(input())
a = []
for _ in range(n):
    a.append(tuple(map(int,input().split())))
a.sort()
f = [1]*n
for i in range(n):
    for j in range(i):
        if a[i][1] > a[j][1]:
            f[i] = max(f[i],f[j] + 1)

print(max(f))

最大上升子序列和

n = int(input())
a = list(map(int,input().split()))
f = a[:]
for i in range(n):
    for j in range(i):
        if a[j] < a[i]:
            f[i] = max(f[i],f[j] + a[i])
print(max(f))

拦截导弹

# 每一发导弹的轨迹都是一个最长非上升子序列,求一发导弹最多能拦截多少个,相当于求最长非上升子序列
import sys
RI = lambda: map(int, sys.stdin.buffer.readline().split())
RS = lambda: map(bytes.decode, sys.stdin.buffer.readline().strip().split())
RILST = lambda: list(RI())
a = RILST()
n = len(a)
f = [1]*n
for i in range(n):
    for j in range(i):
        if a[j] >= a[i]:
            f[i] = max(f[i],f[j] + 1)
print(max(f))

# 求最少用多少个非上升子序列可以覆盖所有导弹
# 389 207 155 300 299 170 158 65
# 389一组导弹,最后高度155
# 300来了之后没法让155更小,只能再发射一颗,此时数组g=[155,300]
# 299-158都可以接在300后面,g=[155,158]
# 65可以接在155后面,g = [65,158]
# 所以最后只需要两个导弹,回顾这个过程,和贪心法求最长上升子序列是一样的
# 65和158,分别是两个最长非递增子序列的末尾元素
# [389 300 299 170 158 65] [207 155]

# 贪心求最长上升子序列长度,最后g里就是每个最长上升子序列的末尾元素
from bisect import bisect_left,bisect_right
g = []
for x in a:
    idx = bisect_left(g,x)
    if idx == len(g):
        g.append(x)
    else:
        g[idx] = x
print(len(g))
# 这题如果导弹高度是严格下降的,那g应该是求最长非下降子序列长度,用bisect_right

187. 导弹防御系统 - AcWing题库

from bisect import bisect_right,bisect_left
while True:
    n = int(input())
    if n == 0:
        break
    else:
        a = list(map(int,input().split()))
        n = len(a)
        up,down = [0]*n,[0]*n # 初始值不重要,我们用jk下标控制了数组范围
        ans = n
        def dfs(i,j,k):
            global ans
            if j + k >= ans:
                return
            if i == n:
                ans = j + k
                return
            # 找第一个小于a[i]的位置,放在上升子序列后
            idx = 0
            while idx < j and up[idx] >= a[i]: # 因为只能拦截更高的,所以低的导弹来了就得发射新的来拦截,数组+1
            #up里每个元素都是一个递增子序列的最后一个元素,如果当前元素大于最后一个元素,直接把最后一个元素变成当前元素,数组长度不会增加
                idx += 1
            t = up[idx]
            up[idx] = a[i]
            if idx < j:
                dfs(i + 1,j,k)
            else:
                dfs(i + 1,j + 1,k)
            up[idx] = t
            
            # 放在下降子序列之后
            # down里每个元素代表一个下降子序列的最后一个元素
            # 如果当前元素小于某个down元素,代表可以接在这个元素的序列后
            idx = 0
            while idx < k and down[idx] <= a[i]:
                idx += 1
            # 找第一个大于a[i]的元素
            #idx = bisect_right(down,a[i],hi = k)
            t = down[idx]
            down[idx] = a[i]
            if idx < k:
                dfs(i + 1,j,k)
            else:
                dfs(i + 1,j,k + 1)
            down[idx] = t
        
        dfs(0,0,0)
        print(ans)
    

LCIS

n = int(input())
from math import inf
# 最长公共上升子序列
# a,b是长分别为m,n的两个数组,找出a,b中公共上升子序列的长度
# dp定义:f[i][j]为a的前i个数、b的前j个数中以b[j]结尾的(可以包含a[j],也可以不包含)最长上升子序列的长度
# 最终答案为max(f[m][j] for j in range(1,n+ 1))
a = [-inf] + list(map(int,input().split()))
b = [-inf] + list(map(int,input().split()))
m,n = len(a) - 1,len(b) - 1
f = [[0]*(n + 1) for _ in range(m + 1)]
for i in range(1,m + 1):
    maxv = 0
    for j in range(1,n + 1):
        # 如果不选a[i]
        f[i][j] = f[i - 1][j]
        # 如果选a[i],那么a[i] == b[j]
        if a[i] == b[j]:
            f[i][j] = max(f[i][j],1 + maxv)
        if a[i] > b[j]: # a[i]可以作为b[j]之后的b[k]匹配
            maxv = max(maxv, f[i - 1][j])
print(max(f[m][j] for j in range(1,n + 1)))

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值