蓝桥杯(4):python动态规划DP[1]

动态规划相当于正着想?dfs主要适用于位置的变化?

子问题!状态,状态转移方程

1 一维DP

1.1 定义

重叠子问题!转换成子问题 ,与记忆化搜索很像

1.2 例子

1.2.1 上楼梯

子问题到最终的问题只能跨一步:比如:4个台阶的话,可以从2或3走上来,但没有1!!!

1到4肯定要跨两步了

其实 就是斐波那契数列

#上楼梯
n = int(input())
dp = [0]*(n+1)
dp[1] = 1
dp[2] = 2
for i in range(3,n+1):
    dp[i] = dp[i-1] + dp[i-2]
print(dp[n])

1.3 步骤

1.4 例题

1.4.1 破损的楼梯

# 破损的楼梯
n,m = list(map(int,input().split()))
a = list(map(int,input().split()))
vis = [0]*(n+1)
for i in a:
    vis[i]=1
dp = [0]*(n+1)
dp[0]=1
dp[1] =1-vis[1]
for i in range(2,n+1):
    if vis[i]==1:
        continue
    dp[i] = dp[i-1]+dp[i-2]
print(dp[n])

1.4.2 安全序列

##安全序列
#输入
n,k = list(map(int,input().split()))
dp = [0]*(n+1)
dp[0] = 1
dp[1] = 2
mod = 10**9+7
for i in range(2,n+1):
    if i-k-1>0:
        #说明和这个位置相隔k的位置是存在的或许可以放置
        #dp[i-1]说明没有放置! dp[i-k-1]说明放置了
        #没有放置,则和前面的方案数一样;放置,则前间隔n个都不能放置,方案数为第(i-k-1)位置上的大小咯
        dp[i] = (dp[i-1]+dp[i-k-1])%mod
    else:
        dp[i] = (dp[i-1]+1)%mod
print(dp[n])

2 二维DP

2.1 定义

2.2 例题

2.2.1 数字三角形

用状态1写

##数字三角形
# 特点:第n行有n个数字
n = int(input())
a = [[0],]
dp = [[0]*(i+1) for i in range(n+1)]
for i in range(n):
    a.append([0] + list(map(int, input().split())))

#用状态1写:(i,j)表示从(i,j)往下走的最大和
#输出的应该是dp[1][1]
for i in range(n,0,-1):
    for j in range(1,i+1):
        if i==n:
            dp[i][j]=a[i][j]
        else:
            dp[i][j] = max(dp[i+1][j],dp[i+1][j+1]) +a[i][j]
print(dp[1][1])

# 5
# 7
# 3 8
# 8 1 0
# 2 7 4 4
# 4 5 2 6 5

用状态2写

##数字三角形
# 特点:第n行有n个数字
n = int(input())
a = [[0],]
dp = [[0]*(n+1) for i in range(n+1)]
for i in range(n):
    a.append([0] + list(map(int, input().split())))

#(i,j)表示到达(i,j)的最大和
for i in range(1,n+1):
    for j in range(1,i+1):
        if i==1:
            dp[1][1] = a[1][1]
        else:
            dp[i][j] = max(dp[i-1][j-1],dp[i-1][j])+a[i][j]

print(max(dp[n]))

2.2.2 摆花

#摆花
n,m = list(map(int,input().split()))
a = [0]+list(map(int,input().split()))
# 摆到第i个花的时候已经有j盆花了
# dp[i][j]
# 第i种花,摆0到i盆
# dp[i][j]= dp[i-1][j]+dp[i-1][j-1]+dp[i-1][j-2] +......+dp[i-1][j-i]
# 边界:dp[0][0] = 1 dp[0][1] =1 dp[0][j] = 0
# dp[1][1]=2
dp = [[1]+[0]*m for i in range(n+1)]

for i in range(1,n+1):
    for j in range(1,m+1): #有几盆花
        for k in range(0,min(a[i],j)+1):
            dp[i][j] += dp[i-1][j-k]
            dp[i][j]%=10**6+7
print(dp[n][m])

2.2.3 选数异或

注意在这个题中我们发现:x的值可以从0取到63说明异或运算不是相同为1 不同为0的运算咯

根据在python中测试后得到:相同两个数异或运算为0,不同是两个数异或运算的值相加即可!

n,x = list(map(int,input().split()))
a = [0]+list(map(int,input().split()))
# 状态dp[i][j]表示前i个数字异或的值是j
# 状态转移: 当第i个数字选择的时候 dp[i][j] = dp[i-1][j^a[i]]
# 当第i个数字没选的时候 dp[i][j] = dp[i-1][j]
dp = [[0]*64 for i in range(n+1)]
dp[0][0] = 1
for i in range(1,n+1):
    for j in range(64):
        dp[i][j] = (dp[i-1][j]+dp[i-1][j^a[i]]) % 998244353
print(dp[n][x])

3 最长上升子序列

3.1 定义

3.1.1 子序列

状态是什么?

后面的数字必须比前面的大!!

比如现在拿到的时a[2](值为4),这时前面比4小的有1和3,对应的长度分别时1和2

就选2 在2的基础上再加1

3.2 例题

3.2.1 蓝桥勇士

n = int(input())
a = [0] + list(map(int,input().split()))
dp = [1]*(n+1)

# i表示选择第i个数字
# j表示前面的数字
for i in range(1,n+1):
    for j in range(1,i):
        if a[j]<a[i]:
            dp[i] = max(dp[j]+1 ,dp[i])

print(max(dp))

注意边界全是1!!!!!!!!刚开始以自己结尾一定选自己,方案数为1!!

3.2.2 合唱队形

# 最高的中间旁边依次降低
# 假设最高的站在第i个位置 i前面是上升序列  i后面是下降序列
n = int(input())
t = [0]+list(map(int,input().split()))
dp1 = [1]*(n+1) #存储最大上升子序列
dp2 = [1]*(n+1) #存储最大下降子序列
for i in range(1,n+1):
    for j in range(1,i):
        if t[j]<t[i]:
            dp1[i] = max(dp1[j]+1,dp1[i])
# print(dp1)
for i in range(n,0,-1):
    for j in range(i+1,n+1):
        if t[i]>t[j]:
            dp2[i] = max(dp2[j]+1,dp2[i])
# print(dp2)
ans = max([dp1[i]+dp2[i]-1 for i in range(1,n+1)])
print(n-ans)


4 最长公共子序列

4.1 定义

对于两个数组而言的

如果dp[i][j]对应的a[i]和b[j]是相等的说明 子序列的长度可以加1,在谁的基础上加1呢,左上角的就都可以!!!!【观察这个矩阵不看边界,可以知道对角线上的元素是一样的】加1肯定是在上一个对角线上咯!!!

如果对应的值不相等 说明这时的值还是上一条对角线对应的值,所以往上或左找 就是他!

怎么找出具体的公共子序列:

可以向上走或者想左走:说明上一条对角线的数和自己相同,没有发生状态转移,则回到这个位置,试图找到发生转移的点

不可以走的时候:说明发生的状态转移,转移一定发生在正对角线上!!!

4.2 例题

# 一个长度为N的数组a
# 一个长度为M的数组b
# 公共子序列的长度最长
# dp[i][j]表示a的第i个数字 b的第j个数字 中最长的公共子序列列长度是dp[i][j]
'''
if a[i]==b[j]:
dp[i][j]= dp[i-1][j-1] #要在前面的基础上加1
else: #说明不相等最长长度不变!
dp[i][j] =max(dp[i-1][j],dp[i][j-1])
'''

def output(a):
    n = len(a)
    for i in range(n):
        print(" ".join(map(str,a[i][0:])))


n = int(input())
a = list(map(int,input().split()))
m = int(input())
b = list(map(int,input().split()))
dp =[[0]*m for i in range(n)]
for i in range(n):
    for j in range(m):
        if i-1>=0 and j-1>=0:
            if a[i] == b[j]:
                dp[i][j] = dp[i-1][j-1]+1
            else:
                dp[i][j] = max([dp[i-1][j],dp[i][j-1]])
        else:
            dp[i][j] = 1
print(dp[n-1][m-1])
output(dp)


# 5
# 1 3 4 2 5
# 5
# 1 4 3 5 2

# 输出其中一个结果呢?
ans = []
i =n-1
j =m-1
while i-1>=-1 and j-1>=-1 :
    if dp[i-1][j]==dp[i][j]:
        i=i-1
        j=j
    else:
        ans.append(a[i])
        i = i-1
        j = j-1

print(list(reversed(ans)))



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值