状态机dp(Python)

状态机dp

例题 1:保险箱(蓝桥杯14届省赛真题)

小蓝有一个保险箱,保险箱上共有 n 位数字。小蓝可以任意调整保险箱上的每个数字,每一次操作可以将其中一位增加 1 或减少 1。当某位原本为 9 或 0 时可能会向前(左边)进位/退位,当最高位(左边第一位)上的数字变化时向前的进位或退位忽略。

例如:
00000 的第 5 位减 1 变为 99999;99999 的第 5 位减 1 变为 99998;00000 的第 4 位减 1 变为 99990;97993 的第 4 位加 1 变为 98003;99909 的第 3 位加 1 变为 00009。保险箱上一开始有一个数字 x,小蓝希望把它变成 y,这样才能打开它,问小蓝最少需要操作的次数。

输入格式:

输入的第一行包含一个整数 n。第二行包含一个 n 位整数 x。第三行包含一个 n 位整数 y。

输出格式:

输出一行包含一个整数表示答案。

数据范围:

对于 30 % 30\% 30% 的评测用例, 1 ≤ n ≤ 300 1≤n≤300 1n300
对于 60 % 60\% 60% 的评测用例, 1 ≤ n ≤ 3000 1≤n≤3000 1n3000
对于所有评测用例, 1 ≤ n ≤ 1 0 5 1≤n≤10^5 1n105,x,y 中仅包含数字 0 至 9,可能有前导零。

解题思路:

  • 核心在于当x与y的相应位之间的差距过大,可以通过向高位进位(借位)缩短距离
  • 需要注意进位借位虽然可以减少当前位上的修改次数,但可能导致高位上需要更多修改
  • 这种不确定性促使了dp的应用,否则程序将退化为贪心算法,无法保证求出最优解
  • 创建dp数组,存储的元素是从最低位到当前位将x变成y所需的修改次数
  • dp的下标有两个,一个用来标识位数,另一个用来表示当前位进位(借位)或直接修改所需的修改次数
n = int(input())
x = [int(i) for i in input()]
y = [int(i) for i in input()]
# dp[i][0]:第i位采用直接修改策略的前提下,区间[i:]所需的修改次数
# dp[i][1]:第i位采用向上进位策略的前提下,区间[i:]所需的修改次数
# dp[i][2]:第i位采用向上借位策略的前提下,区间[i:]所需的修改次数
dp = [[0]*3 for _ in range(n)]
# 特殊处理最低位
dp[n-1][0] = abs(x[n-1]-y[n-1])
dp[n-1][1] = abs(10+y[n-1]-x[n-1])
dp[n-1][2] = abs(10+x[n-1]-y[n-1])
# 从低位向高位搜索
for i in range(n-2,-1,-1):
    p = x[i]
    q = y[i]
    dp[i][0] = min(dp[i+1][0]+abs(p-q), dp[i+1][1]+abs(p+1-q), dp[i+1][2]+abs(p-1-q))
    dp[i][1] = min(dp[i+1][0]+abs(10+q-p), dp[i+1][1]+abs(10+q-p-1), dp[i+1][2]+abs(10+q-p+1))
    dp[i][2] = min(dp[i+1][0]+abs(10+p-q),dp[i+1][1]+abs(10+p+1-q),dp[i+1][2]+abs(10+p-1-q))
print(min(dp[0][0],dp[0][1],dp[0][2]))

例题 2:蜗牛(蓝桥杯14届省赛真题)

问题描述:

这天,一只蜗牛来到了二维坐标系的原点。 在 x 轴上长有 n 根竹竿。它们平行于 y 轴,底部纵坐标为 0 ,横坐标分别为 x 1 , x 2 , . . . , x n x_1, x_2,...,x_n x1,x2,...,xn 。竹竿的高度均为无限高,宽度可忽略。蜗牛想要从原点走到第 n 个竹竿的底部也就是坐标 ( x n , 0 ) (x_n , 0) (xn,0) 。它只能在 x 轴上或者竹竿上爬行,在 x 轴
上爬行速度为 1 单位每秒;由于受到引力影响,蜗牛在竹竿上向上和向下爬行 的速度分别为 0.7 单位每秒和 1.3 单位每秒。 为了快速到达目的地,它施展了魔法,在第 i 和 i + 1 根竹竿之间建立了传送门(0 < i < n ),如果蜗牛位于第 i 根竹竿的高度为 a i a_i ai 的位置 ( x i , a i ) (x_i, a_i) (xi,ai) ,就可以瞬间到达第 i+1 根竹竿的高度为 b i + 1 b_{i+1} bi+1 的位置 ( x i + 1 , b i + 1 ) (x_{i+1}, b_{i+1}) (xi+1,bi+1), 请计算蜗牛最少需要多少秒才能到达目的地。

输入格式:

输入共 1 + n 行,第一行为一个正整数 n ;
第二行为 n 个正整数 x 1 , x 2 , . . . , x n x_1 , x_2 , . . . , x_n x1,x2,...,xn
后面 n − 1 行,每行两个正整数 a i , b i + 1 a_i, b_{i+1} ai,bi+1

输出格式:

输出共一行,一个浮点数表示答案(四舍五入保留两位小数)。

# 状态机dp
# 内存超限
up = 0.7
down = 1.3
n = int(input())
x = [0] + [int(i) for i in input().split()]
a = [0]*(n+1)
b = [0]*(n+1)
for i in range(1,n):
    p,q = map(int,input().split())
    a[i] = p
    b[i+1] = q
# dp[i][0]:到达节点i所需要的最短时间
# dp[i][1]:到达第i个传送门所需要的最短时间
dp = [[0]*(n+1) for i in range(n+1)]
# 初始位置0
dp[1][0] = x[1]
dp[1][1] = x[1] + a[1]/up
for i in range(2,n+1):
    # 到达当前节点有四种方式(包含两个出发点和两条路径)
    d = x[i] - x[i-1]
    # 计算出从上一个传送终点 b[i-1] -> a[i-1] 的路径耗时
    temp = 0
    if b[i-1] > a[i-1]:
        # down
        temp += (b[i-1]-a[i-1])/down
    else:
        temp += (a[i-1]-b[i-1])/up
    dp[i][0] = min(dp[i-1][0]+d,dp[i-1][0]+a[i-1]/up+b[i]/down,dp[i-1][1]+b[i-1]/down+d,dp[i-1][1]+temp+b[i]/down)
    dp[i][1] = min(dp[i-1][0]+d+b[i]/up,dp[i-1][0]+a[i-1]/up,dp[i-1][1]+b[i-1]/down+d+a[i]/up,dp[i-1][1]+temp)
print(f'{dp[n][0]:.2f}')
# 状态机dp + 滚动数组优化
up = 0.7
down = 1.3
n = int(input())
x = [0] + [int(i) for i in input().split()]
a = [0]*(n+1)
b = [0]*(n+1)
for i in range(1,n):
    p,q = map(int,input().split())
    a[i] = p
    b[i+1] = q
# dp[0]:到达节点i所需要的最短时间
# dp[1]:到达第i个传送门所需要的最短时间
dp = [0]*2
# 初始位置0
dp[0] = x[1]
dp[1] = x[1] + a[1]/up
for i in range(2,n+1):
    pre_0 = dp[0]
    pre_1 = dp[1]
    # 到达当前节点有四种方式(包含两个出发点和两条路径)
    d = x[i] - x[i-1]
    # 计算出从上一个传送终点 b[i-1] -> a[i-1] 的路径耗时
    temp = 0
    if b[i-1] > a[i-1]:
        # down
        temp += (b[i-1]-a[i-1])/down
    else:
        temp += (a[i-1]-b[i-1])/up
    dp[0] = min(pre_0+d,pre_0+a[i-1]/up+b[i]/down,pre_1+b[i-1]/down+d,pre_1+temp+b[i]/down)
    dp[1] = min(pre_0+d+b[i]/up,pre_0+a[i-1]/up,pre_1+b[i-1]/down+d+a[i]/up,pre_1+temp)
print(f'{dp[0]:.2f}')
  • 11
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值