当前位置: 首页 > article >正文

DP动态规划

1.背包问题

1.1 0/1背包

 1.1.1经典做法

def solve(N,C):  # 从左到右,从上到下 (先种类,再体积)
    for i in range(1,N+1): # N种物品,先1种,再2种......
        for j in range(0,C+1):  # 当前背包体积
            if c[i]>j : dp[i][j] = dp[i-1][j]
            else: dp[i][j] = max(dp[i-1][j-c[i]]+w[i],dp[i-1][j])  # 装或者不装,找最大值
            
    return dp[N][C]
N,C= map(int,input().split())
n=3000
dp = [[0]*n for i in range(n)]  # 初始化dp数组,预留更大空间
c=[0]*n  # 记录体积
w=[0]*n # 记录价值
for i in range(1,N+1):   #读入N种物品的价值和体积
    c[i],w[i] = map(int,input().split())
print(solve(N,C))

 1.1.2 优化为两行队列存储

def solve(N,C):  # 从左到右,从上到下 (先种类,再体积)
    old = 1
    new = 0
    for i in range(1,N+1): # N种物品,先1种,再2种......
        new,old = old,new  # 交换两行
        for j in range(0,C+1):  # 当前背包体积
            if c[i]>j : dp[new][j] = dp[old][j]
            else: dp[new][j] = max(dp[old][j-c[i]]+w[i],dp[old][j])  # 装或者不装,找最大值
            
    return dp[N][C]
N,C= map(int,input().split())
n=3000
dp = [[0]*n for i in range(2)]  # 初始化dp数组,预留更大空间
c=[0]*n  # 记录体积
w=[0]*n # 记录价值
for i in range(1,N+1):   #读入N种物品的价值和体积
    c[i],w[i] = map(int,input().split())
print(solve(N,C))

1.1.3  优化为数组滚动

def solve(N,C):  # 从左到右,从上到下 (先种类,再体积)
    for i in range(1,N+1): # N种物品,先1种,再2种......
        for j in range(C,c[i]-1,-1):  # 当前背包体积,倒序进行,到c[i]-1,因为范围[c[i],C]
           dp[j] = max(dp[j],dp[j-c[i]]+w[i]) # 选或者不选           
    return dp[C]
 
 
N,C= map(int,input().split())
n=3000
dp = [0]*n  # 初始化dp数组,预留更大空间 一维
c=[0]*n  # 记录体积
w=[0]*n # 记录价值
for i in range(1,N+1):   #读入N种物品的价值和体积,从1开始计数
    c[i],w[i] = map(int,input().split())
print(solve(N,C))

1.2 0/1背包简化版

1.2.1 装箱问题

 把体积看成最优化目标,即最大化体积

def solve(N,C):  # 从左到右,从上到下 (先种类,再体积)
    new = 0
    old = 1
    for i in range(1,N+1): # N种物品,先1种,再2种......
        new,old = old,new
        for j in range(0,C+1):
            if c[i]>j: dp[new][j] = dp[old][j]
            else: dp[new][j] = max(dp[old][j-c[i]]+c[i],dp[old][j])      
    return dp[N][C]
 
 
V = int(input()) # 体积
N = int(input()) # 物品种类
n=21000
dp = [[0]*n for i in range(2)]  # 初始化dp数组,预留更大空间 一维
c=[0]*n  # 记录体积
 
for i in range(1,N+1):   #读入N种物品的价值和体积,从1开始计数
    c[i]=int(input())
print(V-solve(N,V))

数组滚动做法

def solve(N,C):  # 从左到右,从上到下 (先种类,再体积)
    for i in range(1,N+1): # N种物品,先1种,再2种......
        for j in range(C,c[i]-1,-1):  # 当前背包体积,倒序进行,到c[i]-1,因为范围[c[i],C]
           dp[j] = max(dp[j],dp[j-c[i]]+c[i]) # 选或者不选           
    return dp[C]
 
 
V = int(input()) # 体积
N = int(input()) # 物品种类
n=21000
dp = [0]*n  # 初始化dp数组,预留更大空间 一维
c=[0]*n  # 记录体积
 
for i in range(1,N+1):   #读入N种物品的价值和体积,从1开始计数
    c[i]=int(input())
print(V-solve(N,V))

 1.2.2 0/1背包的方案数

# dp[i][j][k] 从数字1~i中取j个,和为k的方案数
dp = [[[0]*2222 for i in range(11)] for j in range(2222)]
for i in range (0,2023):   # 初始化
    dp[i][0][0]=1
for i in range(1,2023):
    for j in range(1,11):
        for k in range (1,2023):
            if k<i:  # 取不到i
                dp[i][j][k] = dp[i-1][j][k]
            else:  # 没有取i + 取i的情况
                dp[i][j][k]=dp[i-1][j][k]+dp[i-1][j-1][k-1]
print(dp[2022][10][2022])

用滚动数组

# dp[i][j][k] 从数字1~i中取j个,和为k的方案数
dp = [[0]*2222 for i in range(11)]
dp[0][0]=1
for i in range(1,2023):
    for j in range(10,0,-1):  #10个数
       for k in range(i,2023):  # 只考虑k>i的情况,k<i的情况直接不变
                dp[j][k]+=dp[j-1][k-1]
print(dp[10][2022])

1.3   完全背包

 CSDN优质文章讲解背包问题

# 代码与0/1背包相似,多了一个k循环用来遍历每种物品拿几个
# dp[i][j] 把前i种物品装入容量为j的背包所获得的最大价值
def solve(n, C):  # 从左到右,从上到下 (先种类,再体积)
    for i in range(1, n + 1):  # N种物品,先1种,再2种......
        for j in range(0, C + 1):
            # if i==1:
            #     dp[i][j]=0
            # else:
            #     dp[i][j]=dp[i-1][j]
            # for k in range(0, j // c[i] + 1):     # k * c[i] <=j  # 在容量为j的背包中放k个
            #     dp[i][j] = max(dp[i][j], dp[i-1][j - k * c[i]] + k * w[i])
            dp[i][j] = dp[i - 1][j]
            if j > c[i]:  # 看装几个可以获得最大价值
                for k in range(0, j // c[i] + 1):  # k * c[i] <=j  # 在容量为j的背包中放k个
                    dp[i][j] = max(dp[i][j], dp[i - 1][j - k * c[i]] + k * w[i])
    return dp[N][C]


N=3011
dp=[[0]*N for j in range(N) ]
w=[0]*N
c=[0]*N
n,C=map(int,input().split())
for i in range(1,n+1):
    c[i],w[i]=map(int,input().split())
print(solve(n,C))

1.4   分组背包

思路为0/1背包,完全背包的结合,三重循环 

# 状态转移方程 dp[i][j] = max{dp[i-1][j] , dp[i-1][j-c[i][k]]+w[i][k] }
# 滚动数组    dp[j]=max{dp[j] , dp[j-c[i][k]]+w[i][k] }

dp=[0]*N
for i in range(1,n+1):  # 遍历每个组
    for j in range(C,-1,-1) :  # 遍历每个容量
        for k in range(1,C+1):  # 用k枚举第i组的所有物品
            if (j>=c[i][k]):  # 第k个物品能够转入容量为j的背包
                dp[j]=max( dp[j] , dp[j-c[i][k]] + w[i][k] )   # 不装 或者装 第i组第k个
print(dp[C])

1.5    多重背包

1.5.1 转为0/1背包问题

1.5.2 转为与完全背包相似的问题

 2. 线性DP

 2.1 最长公共子序列

 

# 当 ai=bj 时,状态转移方程 dp[i][j] = dp[i-1][j-1] +1
# 当 ai!=bj时,状态转移方程 dp[i][j] = max{ dp[i][j-1] , dp[i-1][j]}

n,m = map(int,input().split())  # B n个元素 A m个元素
a = [0] + list(map(int,input().split()))
b = [0] + list(map(int,input().split()))
dp = [[0]*(m+1) for _ in range(2)]   # 注意这里是m,不是n,进行初始化
now = 0 ;old = 1
for i in range(1,n+1):  # B的长度1-n遍历 
    now,old = old,now
    for j in range(1,m+1):  # A的长度 1-m遍历
        dp[now][j] = max(dp[now][j-1],dp[old][j])  #继承以前的最大值 
        if a[i]==b[j]:
            dp[now][j] = dp[now][j]+1
print(dp[now][m])

2.2 最长递增子序列 LIS

 

 

N =int(input()) # 对手个数
a = [0]+[int(i) for i in input().split()]  # 记录对手战力值
dp = [0]*(N+1) # 记录以第i个数为结尾的最长递增子序列
dp[1]=1
for i in range(2,N+1):  # 从2-N循环
    for j in range(1,i): # 查找前面的比a[i]小的
        if a[j]<a[i] and dp[j]>dp[i]:   #找到小的同时给他赋值max(dp[j])
            dp[i]=dp[j]
        dp[i]+=1  # 加1,即本身
max(dp)

2.3 编辑距离,字符串转换

"""
A转换为B
删除: A删除最后一个    dp[i][j]=dp[i-1][j]+1
插入: B末尾插入相同的  dp[i][j]=dp[i][j-1]+1
替换: A的末尾直接替换  dp[i][j]=dp[i-1][j-1]+1
"""
# dp[i][j] 表示A的前i个字符转换B的前j个字符所需要的操作步骤
# 将长度为m的A存储在a[1]~a[m]中,不用a[0]和b[0]
"""转移方程"""
# a[i]=b[j],则dp[i][j] = dp[i-1][j-1]
# 其他情况 dp[i][j] = min{dp[i-1][j-1], dp[i-1][j],dp[i][j-1]} +1
a=' '+input()
b=" "+input()
m=len(a)-1
n=len(b)-1
dp = [[0]*(n+1) for i in range(m+1)]
for i in range(1,m+1):dp[i][0]=i
for j in range(1,n+1):dp[0][j]=j
for i in range(1,m+1):   # 遍历a的每一个
    for j in range(1,n+1):  # 遍历b的每一个
        if a[i]==b[j]:
            dp[i][j]=dp[i-1][j-1]
        else:
            dp[i][j]=min([dp[i-1][j],dp[i][j-1],dp[i-1][j-1]])+1
print(dp[m][n])

 2.3.1网格上的DP(过河卒)

x1,y1,x2,y2 = map(int,input().split())  # B点坐标和马的坐标
dp = [[0]*25 for i in range(25)]
s = [[0]*25 for i in range(25)]
x1+=2;y1+=2;x2+=2;y2+=2  # 相当于从(2,2)开始的,坐标轴左移2,右移动2
dp[2][1] = 1 # 初始化,也可以 dp[1][2]=1
# 将马可到达的点全部置为1,即不可达
s[x2][y2]=1
s[x2-2][y2-1]=1;s[x2-2][y2+1]=1;s[x2+2][y2-1]=1;s[x2+2][y2+1]=1;
s[x2-1][y2-2]=1;s[x2-1][y2+2]=1;s[x2+1][y2-2]=1;s[x2+1][y2+2]=1;
for i in range(2,x2+1):
    for j in range(2,y2+1):
        if s[i][j]==1:
            dp[i][j]=0
        else: dp[i][j] = dp[i-1][j]+dp[i][j-1]  # 上面的路径加上左边的路径
print(dp[x1][y1])

 2.4 DP例题

2.4.1 排列数

暴力法,使用内置排列组合

from itertools import *   # 导入itertools 使用排列函数 permutations(list)
n,k = map(int, input().split())  
nums = [i for i in range (1, n+1)]  #1~n
cnt = 0
for num in permutations (nums):     #检查每个排列
    tmp = 0  #记录有多少个折点
    for i in range (n-2) :
        if num[i+1]>num[i+2] and num[i+1]>num[i]:
            tmp += 1 #凸折点
        elif num[i+1]<num[i+2] and num[i+1]<num[i]:
            tmp += 1 #凹折点
    if tmp == k-1 : cnt+=1
print(cnt % 123456)
 

 DP动态规划做法

2.4.2 数字三角形

n = int(input())
a = [list(map(int,input().split())) for i in range(n)]
# 数组a[][] 同时当成dp[][]用
# 从上到下遍历
for i in range(1,n): #循环n-1次
    for j in range(0,i+1): # 第i层有i个元素
        if j==0:
            a[i][j]+=a[i-1][j]
        elif j==i:
            a[i][j]+=a[i-1][j-1]
        else:
            #a[i][j]=max(a[i-1][j-1],a[i-1][j])
            a[i][j]+=max(a[i-1][j-1:j+1])
if n&1: #奇数,位运算
    print(a[-1][n//2])   # 1 2 3   # 3//2=1
else:
    print(max(a[-1][n//2-1],a[-1][n//2]))  # 1 2 3 4  #4//2=2

3 状态压缩DP(Python考的概率很小)

3.1 含义和表示

结合位运算,通过二进制来表示有或者没有。

"""
<< 右移运算
a=1<<(n-1)  即右移n-1位
1<<2        100

通过或运算实现相加去重
temp |=(1<<(n-1))

1<<2    100
temp=10
temp |=(1<<2)    110
"""

"""
1<<(i-1) &a  判断第i为是否为1
a|(1<<(i-1))   将第i为改为1
a&(a-1)   去掉最后一个1
快速幂需要借鉴位运算

"""

3.2 状态DP例题糖果

 例如有  2 3 5三种口味,可以用 10110 表示!

 

import sys
import collections
import itertools
import heapq
sys.setrecursionlimit(300000)


n,m,k = map(int,input().split())
tot=(1<<m)-1   # tot: 二进制是m个1,表示所有m种口味
dp=[-1 for _ in range(1<<20)]   # 全部初始化为-1,同时将大小设置大一点
dp[0]=0  #
kw = []
for _ in range(n):  # 存糖果
    kw.append([int(i) for i in input().split()])

for c in kw:  # 遍历每一包糖果
    temp=0
    for x in c:   #[ 2 ,3 ,4]
        temp|=(1<<(x-1))   # 状态压缩
    for i in range(tot+1):    # 口味是从0-tot
        if (dp[i]==-1): # 没有得到过该种口味,直接跳过
            continue
        newcase = temp| i   # 得到新的口味
        if (dp[newcase] ==-1 or (dp[newcase]>dp[i]+1)):  # 找到更少的口味组合
            dp[newcase]=dp[i]+1
print(dp[tot])

# 有点类似 Dijstra , SPFA , BEll_Floyd

 3.3 矩阵计数例题

# 0 用0表示  X 用1表示
# 横,竖不能出现连续的3个1

# dp[i][j][k] 第i行的合法状态为j,前一行为k
def check(x):
    num=0
    while(x):
        if x&1: num+=1   #发现一个1
        else:   num=0    #不是连续的重新计数
        if num==3:
            return False
        x>>=1   # 右移一次
   
N,M = list(map(int,input().split()))
s=2**M
row =[]
for i in range(s):  #可能的范围 0000-1111
    if check(i):
        row.append(i)
dp=[[[0 for k in range(s)] for j in ragne(s)] for k in range(N)]
for i in row:
    dp[0][i][0]=1
for i in range(1,N):   # 遍历每一行
    for j in row:   # 连续检测三列是否合法
        for k in row:
            for p in row:
                if j&k&p==0:
                    dp[i][j][k]+=dp[i-1][k][p]
ans=0
for j in row:
    ans+=sum(dp[N-1][j])
print(ans)

3.4 回路计数例题(没看懂)

 

from math import gcd
m=1<<22
dp=[[0 for j in range(22)] for i in range(m)]
dist = [[False for j in range(22)] for i in range(22)]    # 矩阵存图
for i in range(1,22):
    for j in range(1,22):
        if gcd(i,j)==1:
            dist[i][j]=True
dist[2][1]=1
for S in range(2,m-1):  # 10-111110
    for j in range(1,22):
        if S>>j&1:
            for k in range(1,22):
              if S-(1<<j)>>k &1 and dist[k][j]:
                  dp[S][j]+=dp[S-(1<<j)][k]

ans=0
for i in range(2,22):
    ans+=dp[m-2][i]
print(ans)

4 树形DP

DFS(不用vis数组)+ 树结构

 4.1 生命之树 

 

 即要我们找最大子树

import sys
import collections
import itertools
import heapq
sys.setrecursionlimit(300000)



def dfs(u,fa):
    global ans
    for son in t[u]:
        if son !=fa:
            dfs(son,u)  # 遍历搜索
            if dp[son]>0:  # 子节点大于0,父结点就添加
                dp[u]+=dp[son]
    ans=max(ans,dp[u])  

n=int(input())
dp=[0]*(n+1)
dp[1:n]=map(int,input().split())   # 不用dp[0]
t=[[] for i in range(n+1)]  # tree,临接表矩阵
for i in range(n-1):  # 存n-1条边
    u,v=map(int,input().split())
    t[u].append(v)  #记录邻居
    t[v].append(u)
ans=0
dfs(1,-1)
print(ans)

#tt=[[]*10]  这样创建里面只有1个
#tt=[[] for i in range(10)]

4.2 大臣的旅费例题

 两次DFS有贪心的思想,第一次DFS找到最远的,第二次一定找到最远的(不适合有负数的情况)

树形DP只要一次DFS

 给出两种实现方式,要标记数组和不要标记数组,都是只遍历一次,保证不回头遍历

import sys
import collections
import itertools
import heapq
sys.setrecursionlimit(300000)

##def dfs(u):
##    global ans
##    vis[u]=1   #标记为访问过
##    for v,c in e[u]:  # 遍历u的邻居v,费用c
##        if vis[v]==1:
##            continue
##        dfs(v)
##        ans = max(ans ,dp[u]+dp[v]+c)
##        dp[u]=max(dp[u],dp[v]+c)
##
##n=int(input())
##e=[[] for i in range(n+1)]  # 存图
##for i in range(n-1):
##    a,b,c=map(int,input().split())
##    e[a].append((b,c))  # 临接表存边
##    e[b].append((a,c))
##
##ans=0
##vis=[0 for i in range(n+1)]
##dp=[0 for i in range(n+1)]
##dfs(1)
##print(ans*10 + ans*(ans+1)//2)  # 等差数列求和


def dfs(u,fa):
    global ans
    for v,c in e[u]:  # 遍历u的邻居v,费用c
        if v!=fa:
            dfs(v,u)
            ans = max(ans ,dp[u]+dp[v]+c)
            dp[u]=max(dp[u],dp[v]+c)

n=int(input())
e=[[] for i in range(n+1)]  # 存图
for i in range(n-1):
    a,b,c=map(int,input().split())
    e[a].append((b,c))  # 临接表存边
    e[b].append((a,c))

ans=0
dp=[0 for i in range(n+1)]
dfs(1,-1)
print(ans*10 + ans*(ans+1)//2)  # 等差数列求和

5 数位DP

5.1 数位DP介绍

dp[ i ] :i位数的每种数码有多少个,将0也看成占位符,后期再特别处理。

例如一位数:0-9

        两位数:00-99

        三位数:000-999

dp[ i ] 的计算

递推计算 

 排列组合计算(总共10**i个数字,每个数字上面有i位,10个数均等出现)

 

5.2 统计所有数码的出现个数 

 

 

 

 

import sys
import collections
import itertools
import heapq
sys.setrecursionlimit(300000)


def solve(x):   #计算[1,x]中的每个数码的个数
    n=tuple(map(int,str(x)))  #把一个数按位分解 123 —> (1,2,3)
    n=n[::-1]  # (1,2,3)—> (3,2,1)
    for i in range(len(n)-1,-1,-1):  # 从高到低处理x的每一位 (2,1,0)
        for j in range(10):
            cnt[j]+=dp[i]*n[i]
        for j in range(n[i]):   # (1)特判最高位比n[i]小的数字
            cnt[j]+=ten[i]
        u=0
        for j in range(i-1,-1,-1):
            u=u*10+n[j]
        cnt[n[i]]+=u+1   #(2)特判最高位的数字n[i],
        cnt[0]-=ten[i]   # 特判前导0  ,对前导0去除  
            

ten=[0]*15
ten[0]=1    # ten[i] : 10的i次方
dp=[0]*15
for i in range(1,15):   #预计算dp[]
    # dp[i]=i*10**(i-1)  这个和下面一样,但是运算速度比下面慢
    dp[i] = i*ten[i-1]  # 或者dp[i]=dp[i-1]*10+ten[i-1]
    ten[i]=ten[i-1]*10
b=int(input())
cnt=[0]*15      # 答案cnt[i] ,数字“i”出现了多少次
solve(b)
for i in range(10):
    print(cnt[i],end=" ")  # 打印每个数码出现的次数


http://www.kler.cn/a/17378.html

相关文章:

  • 解决表格出现滚动条样式错乱问题
  • 红帽认证和华为认证哪个好?看完这4点你就明白了
  • vwmare虚拟机繁忙的解决办法
  • 【AI技术对电商的影响】
  • Apache ECharts
  • 基于Python的膳食健康系统
  • 1.rabbitMQ介绍
  • JavaScript闭包的基本原理和应用场景
  • 人的全面发展评价指标体系—基于相关-主成分分析构建
  • 2000-2019年30省研发资本存量(含计算过程和原始数据)
  • 大数据Doris(八):Broker部署和集群启停脚本
  • 高效学习方法和工具推荐,让你事半功倍!
  • clickhouse里的数组数据类型与相关使用介绍
  • 【C++复习1】程序结构和C++的工作原理
  • Java程序设计入门教程--数组
  • 小球下落(dropping balls)uva679
  • go 打包文件夹成zip文件
  • Envoy控制面实践
  • 漫画 | Linux之父:财务自由以后,我失眠了!
  • 华为OD机试 - 整理扑克牌(Python)
  • [计算机图形学]光场,颜色与感知(前瞻预习/复习回顾)
  • 对比 LVS 负载均衡群集的 NAT 模式和 DR 模式,基于 CentOS 7 构建 LVS-DR 群集
  • springboot 集成 shardingSphere 加mybatisplus 自带增加 分页查询 和源代码包 分库分表 单库 分表 使用雪花算法id
  • node.js 处理路径问题
  • VR与AR:哪个有更大的潜力改变未来?
  • 今天面了个字节跳动拿35K出来的,真是砂纸擦屁股,给我露了一手啊