P9752 [CSP-S 2023] 密码锁 题解

文章讲述了在密码锁考试中,如何通过分析密码锁的结构(5个拨圈),利用递归深度优先搜索(DFS)方法,判断给定的密码状态是否为正确密码的解题思路。

P9752 [CSP-S 2023] 密码锁 题解

为什么考场上一大堆人读错题了啊。顺便祝提高组出题人身体健康。

解法

注意到密码锁只有 555 个拨圈,所以正确密码只有 00000000000000099999999999999910510^5105 种可能,对于每种可能的正确密码,我们只需要 O(n)\mathcal{O}(n)O(n) 进行判断就可以了。

我们设我们枚举到的正确密码状态是 nownownow,其中 nowi(i∈[1,5])now_i(i\in[1,5])nowi(i[1,5]) 表示每个拨圈的状态。我们分别验证 nownownow 可不可以拨到每种锁车后的状态。验证方法如下:

  • 如果当前状态和当前锁车后的状态有至少 333 个拨圈不相同,则因为转动密码锁仅一次,所以不能当前状态不可能是正确密码。
  • 如果当前状态和当前锁车后的状态相同,则因为这 nnn 个状态都不是正确密码,所以当前状态不可能是正确密码。
  • 如果当前状态和当前锁车后的状态有 222 个拨圈不相同,那么如果两个状态不相同的拨圈位置相邻且拨动幅度相同,则当前状态可能是正确密码(还要看是否满足其他锁车后的状态),否则一定不是正确密码。
  • 如果当前状态和当前锁车后的状态有 111 个拨圈不相同,则当前状态可能是正确密码(还要看是否满足其他锁车后的状态)。

考场代码

#include<bits/stdc++.h>
using namespace std;
int n;
long long ans;
int a[100][10],now[10],sub[10];
inline bool judge()
{
    for(int i=1,cnt;i<=n;i++)
    {
        cnt=0;
        for(int j=1;j<=5;j++) sub[j]=(a[i][j]-now[j]+10)%10;
        if(sub[1]==0 && sub[2]==0 && sub[3]==0 && sub[4]==0 && sub[5]==0) return false;
        for(int j=1;j<=5;j++)
        {
            if(sub[j]==0) continue;
            cnt++;
            if(cnt>=3) return false;
            if(cnt==2)
            {
                if(sub[j]!=sub[j-1]) return false;
            }
        }
    }
    return true;
}
inline void dfs(int dep)
{
    if(dep==6)
    {
        if(judge()) ans++;
        return;
    }
    for(int i=0;i<=9;i++) now[dep]=i,dfs(dep+1);
}
int main()
{
    // freopen("lock.in","r",stdin);
    // freopen("lock.out","w",stdout);
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++) for(int j=1;j<=5;j++) cin>>a[i][j];
    dfs(1);
    cout<<ans;
    return 0;
}
### 题目分析 CSP - S 2023 密码锁题目中,小 Y 操作的密码锁有 5 个拨圈,每个拨圈有 0 - 9 共 10 种状态。当小 Y 选择同时转动两个相邻拨圈时,两个拨圈转动的幅度相同,例如可以将密码锁从 0 0 1 1 5 转成 1 1 1 1 5,但不会转成 1 2 1 1 5 [^2]。对于一种状态,其可能产生的其他状态有两种情况:只转一个拨圈能产生 5 * 9 = 45 种密码;转相邻的两个拨圈能产生 4 * 9 = 36 种密码,所以一种状态能产生的密码共有 45 + 36 = 81 种。题目给出了 n (n ≤ 8) 个状态,要求找出能满足所有这些状态的密码有多少种 [^3]。 ### 解题思路 由于密码只有 5 位,总共的情况有 \(10^5\) 种。对于每种密码,只需判断它是否能到达给出的 n 个状态。判断时,遍历 n 个状态,对每个状态分别进行转一个拨圈的变换和转两个拨圈的变换,看是否能够得到该密码,若 n 个状态都符合条件,则答案加一,该复杂度是可以接受的 [^3]。 ### 代码实现 #### 代码一 ```cpp #include<bits/stdc++.h> using namespace std; const int maxn = 10; int n, stat[maxn][7], v[7], ans; inline int dis(int a, int b) { // 计算a、b在密码锁上的距离 return (a - b + 10) % 10; } int chk() { // 判断可行性 for(int i = 1; i <= n; i++) { // i为当前为第i个序列 // ok为标志位,代表是否有不同 // 如有不同则为false,没有则为true bool ok = true; for(int j = 1; j <= 5; j++) { // j表示当前正在判断第i个序列的第j个元素 // 判断是否有不同 if(stat[i][j] != v[j]) { // 如果已有不同,代表不同超过1次 // 代表该序列不合法 if(!ok) return false; // 判断是否为同时转的情况 if(dis(v[j], stat[i][j]) == dis(v[j + 1], stat[i][j + 1])) { j++; } // 将标志位置为false,代表有不同 ok = false; } } // ok为true代表两序列完全相同 if(ok) return false; } return true; } // 注意无返回值函数声明为void void dfs(int st) { // 枚举所有可行性,也可以写五层循环 // 递归边界 if(st == 6) ans += chk(); else for(int i = 0; i <= 9; i++) { // 枚举下一层 v[st] = i; dfs(st + 1); } } int main() { // 主函数,这里考试时要freopen // 读入数据 cin >> n; for(int i = 1; i <= n; i++) for(int j = 1; j <= 5; j++) cin >> stat[i][j]; // 枚举+输出 dfs(1); cout << ans; return 0; } ``` 此代码通过深度优先搜索(DFS)枚举所有可能的密码组合,对于每个组合调用 `chk` 函数判断是否满足条件,满足则答案加一 [^1]。 #### 代码二 ```cpp #include<bits/stdc++.h> using namespace std; int a[10][10]; int p[10]; int main() { int n, ans = 0; cin >> n; for(int i = 1; i <= n; i++) { for(int j = 1; j <= 5; j++) { cin >> a[i][j]; } } for(p[1] = 0; p[1] <= 9; p[1]++) { for(p[2] = 0; p[2] <= 9; p[2]++) { for(p[3] = 0; p[3] <= 9; p[3]++) { for(p[4] = 0; p[4] <= 9; p[4]++) { for(p[5] = 0; p[5] <= 9; p[5]++) { bool flag = 0; for(int i = 1; i <= n; i++) { int cnt = 0; for(int j = 1; j <= 5; j++) { if(a[i][j] != p[j]) { cnt++; } } if(cnt >= 3 || cnt == 0) { flag = 1; break; } if(cnt == 1) continue; for(int j = 1; j <= 5; j++) { if(a[i][j] != p[j]) { if(a[i][j + 1] == p[j + 1]) { flag = 1; break; } if((a[i][j] + 10 - p[j]) % 10 == (a[i][j + 1] + 10 - p[j + 1]) % 10) { break; } else { flag = 1; break; } } } if(flag == 1) break; } if(flag == 0) ans++; } } } } } cout << ans; return 0; } ``` 该代码使用五层循环枚举所有可能的密码组合,对于每个组合检查是否满足所有给出的状态,满足则答案加一 [^4]。 ### 复杂度分析 - **时间复杂度**:由于需要枚举 \(10^5\) 种密码组合,对于每种组合需要遍历 n 个状态进行判断,所以时间复杂度为 \(O(n\times10^5)\),在 n ≤ 8 的情况下是可以接受的。 - **空间复杂度**:代码中使用的数组大小是固定的,因此空间复杂度为 \(O(1)\)。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值