密码锁 (动态规划)

· · 个人记录

题目描述

密码锁由n个从左到右并排的圆环组成,每个圆环上都有10个数字(0~9),中括号内为密码显示区,每个圆环在密码显示区只能显示一个数字。可以拨动圆环来改变密码显示区显示的数字。当密码显示区的数字与密码一致时,密码锁就会被打开。

编程实现

有一个由n个圆环组成的密码锁,和一个n位的密码S(S由1~9的数字(包含1和9)组成)。每次操作只能选择一个或位置连续的多个圆环拨动。当S中的字符从左到右依次显示在密码显示区时,密码锁会被打开。

已知每个圆环在密码显示区初始数字都为0,请计算出最少需要操作多少次,才能打开密码锁。

注意:

  1. 如果选择了其中一个圆环,可将该圆环中任意一个数字拨动到密码显示区,表示1次操作;例如:将第3个圆环拨动到数字4,表示1次操作:
     2  6  8       2   6    3
    [3  7  9] —>  [3   7   4]
     4  8  0       4   8    5
  2. 如果选择了位置连续的多个圆环,只能将这些圆环拨动成同一个数字,显示在密码显示区,表示1次操作。例如:将连续的第2个到第3个圆环都拨动到数字5,表示1次操作:
     2  6  8       2   4   4
    [3  7  9] —>  [3   5   5]
     4  8  0       4   6   6

例如:
n =5,S="12321";分别表示5个圆环组成的密码锁和密码12321;将5位密码1、2、3、2、1从左到右依次显示在密码显示区,以下是操作次数最少的方案:
第一次操作,将5个初始状态为0的圆环全部拨动到数字1:

 0 0 0 0 0
[1 1 1 1 1]
 2 2 2 2 2

第二次操作,将第2个到第4个圆环全部拨动到数字2:

 0 1 1 1 0
[1 2 2 2 1]
 2 3 3 3 2

第三次操作,将第3个圆环拨动到数字3:

 0 1 2 1 0
[1 2 3 2 1]
 2 3 4 3 2

最少需要操作3次,才能打开密码锁。

输入描述

第一行输入一个整数n(1<=n<=100),表示密码锁的圆环数及密码的位数
第二行输入一个长度为n的字符串S,S由1~9中的数字(包含1和9)组成,表示密码

输出描述

输出一个整数,表示最少需要操作多少次,才能打开密码锁

示例

输入:
5
12321

输出:
3

题解

要使用动态规划解决这个密码锁问题,我们需要定义合适的状态和状态转移方程,并从基本情况出发逐步构建到最终解。以下是一种可能的动态规划方法来解决这个问题:

1. 定义状态

定义 dp[i][j] 为从密码锁的第 i 个圆环到第 j 个圆环(包括 ij)调整到密码所需的最小操作次数。由于密码的每个数字可以独立调整,我们需要考虑单个数字调整以及一系列连续数字调整的情况。

2. 状态转移方程

状态转移方程考虑两种情况:单独调整一个圆环或同时调整多个连续的圆环。因此,我们可以将问题分解为更小的子问题:

因此,状态转移方程可以表示为:

状态转移方程: 如果S[k] == S[j] : dp[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j] - 1)

否则,动态规划方程为: dp[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j])

其中,i ≤ k < j,这意味着我们尝试将区间 [i, j] 分割成两部分 [i, k][k+1, j],并对这两部分分别进行调整,最后加上将整个区间调整到一致所需的操作次数。

3. 基本情况

i = j 时,dp[i][j] = 1,因为调整单个圆环到目标数字只需要一次操作。

4. 实现步骤

示例代码

#include <bits/stdc++.h>
using namespace std;
int n;
string S;
int minOperations() {
    int dp[2005][2005] = {};
    // 预处理,直接设置单个圆环的操作次数为1
    for (int i = 0; i < n; ++i) dp[i][i] = 1;
    // 处理长度大于1的区间
    for (int len = 2; len <= n; ++len) {
        for (int i = 0; i <= n - len; ++i) {
            int j = i + len - 1;
            dp[i][j] = len; // 初始化为区间长度,即最坏情况下的操作次数
            for (int k = i; k < j; ++k) {
                if (S[k] == S[j]) dp[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j] - 1);
                else dp[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j]);
            }
        }
    }
    return dp[0][n-1];
}
int main() {
    cin >> n >> S;
    cout << minOperations() << endl;
    return 0;
}