ccxk
2023-11-01
点 赞
0
热 度
27
评 论
0

【2023 CSP-S】密码锁

文章摘要

智阅GPT

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

题目大意

题目描述了一个密码锁的情景,密码锁由五个拨圈组成,每个拨圈上有从​0​9的数字。小Y在设置密码时只会拨动一次,且只会拨动一个拨圈或使用相同的力度拨动相邻的两个拨圈。小Y担心密码锁的安全性,因此他尝试了一些不同的操作来解锁密码锁,将这些操作记录下来。现在要求你找出所有可能的正确密码,这些正确密码可以通过小Y所采用的锁车方式生成小Y记录的所有​n个状态。

题目分析

这道题的关键是要找出所有可能的正确密码,使得每个正确密码都能通过给定的锁车方式生成​n个状态。在题目描述中,给出了两种操作方式,一种是转动一个拨圈,一种是同时转动两个相邻拨圈。所以我们需要遍历所有可能的​5个拨圈的组合,找出满足条件的正确密码。

部分分解法(骗分解法)

以下是原题的测试点范围:

测试点 ​n\leq 特殊性质
​1\sim 3 ​1
​4\sim 5 ​2
​6\sim 8 ​8 A
​9\sim 10 ​8

特殊性质 A:保证所有正确密码都可以通过仅转动一个拨圈得到测试数据给出的 ​n个状态。

30pts解法

测试数据第一组:​n=1,固定有​81种正确密码,分别是转动​1次是​5\times9=45 种,转动​2​4\times9=36 种,所以总共是​81种,输入完成后直接输出 81即可。

#include<bits/stdc++.h>
#define N 11
using namespace std;
int n;
int main(){
    scanf("%d",&n);
    printf("81");
    return 0;
}

60pts解法

​30pts的基础上,可以根据骗分的方法,有这样一种思路:特殊性质A中描述只会存在拨动一次的情况,所以 ​n\le8 的所有状态肯定同时只存在一位数字不同。所以正确密码的数量应该为 ​10-n

所以我们可以分情况讨论,代码如下:

#include<bits/stdc++.h>
#define N 11
using namespace std;
int n;
int main(){
    scanf("%d",&n);
    if(n==1){
        printf("81");
    }
    else{
        printf("%d",10-n);
    }
    return 0;
}

短短几行拿下​60分,是不是非常的轻松呢~

正解思路

  1. 遍历所有可能的​5个拨圈的数字组合,这里我们可以使用​5个嵌套的循循环,每个循环从​0​9,代表每个拨圈的数字。
  2. 对于每种组合,遍历​n个状态,检查是否满足通过给定的锁车方式生成这​n个状态。这里我们定义一个函数 check来判断是否满足条件。
  3. check函数中,我们首先统计每个拨圈需要改变的数字的个数,如果个数为​1,说明只需要转动一个拨圈;如果个数为​2,说明需要同时转动两个相邻的拨圈。我们可以通过计算每个拨圈对应数字的差值来判断是否可以通过同时转动两个相邻拨圈来达到目标状态。
  4. 如果 check函数返回true,说明这种组合是一个可能的正确密码,增加答案的计数。
  5. 最后输出答案即可。

AC代码

#include<bits/stdc++.h>
#define N 11
using namespace std;

int n, a[N][N], b[N], ans;

// 检查是否通过锁车方式生成给定状态
bool check(int i) {
	int cnt = 0;
	for (int j = 1; j <= 5; j++) {
		cnt += (a[i][j] != b[j]);
	}
	// 如果只有一个数字不同,可以通过转动一个拨圈达到目标状态
	if (cnt == 1) return true;
	// 如果数字不同的个数不是1也不是2,不满足条件
	else if (cnt != 2) return false;
	// 如果有两个数字不同,检查是否可以通过同时转动两个相邻拨圈达到目标状态
	for (int j = 1; j < 5; j++) {
        // 分别判断拨动前当前状态和正确密码是否相同并且拨动后两密码是否可以相同
		if (a[i][j] != b[j] && a[i][j + 1] != b[j + 1] && (b[j] - a[i][j] + 10) % 10 == (b[j + 1] - a[i][j + 1] + 10) % 10) {
			return true;
		}
	}
	return false;
}

// 检查是否所有n个状态都满足条件
bool checkmain() {
	for (int i = 1; i <= n; i++) {
		if (!check(i)) return false;
	}
	return true;
}

int main(){
	cin >> n;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= 5; j++) {
			cin >> a[i][j];
		}
	} 
	// 暴力枚举5个数字
	for (int i = 0; i <= 9; i++) {
		for (int j = 0; j <= 9; j++) {
			for (int k = 0; k <= 9; k++) {
				for (int l = 0; l <= 9; l++) {
					for (int o = 0; o <= 9; o++) {
						int num = 0; // 使用数组存储当前枚举的5位数
						b[++num] = i;
						b[++num] = j;
						b[++num] = k;
						b[++num] = l;
						b[++num] = o;
						// 如果满足条件,增加正确密码的个数
						if (checkmain()) {
							ans++;
						}
					}
				}
			}
		}
	}
	cout << ans;
	return 0;
}

时间复杂度分析

在主循环中,我们有​5个嵌套循环,每个循环的范围是​0​9,所以共循环​10^5次。每一个密码的状态数一共​81种,所以时间复杂度应为​O(8.1\times 10^6),因此不会超时。

空间复杂度分析

  1. 除了输入数据和输出数据外,主要使用了两个数组 ab,以及一个整数 ans。这三个的空间复杂度是​O(n) + O(5) + O(1) = O(n)
  2. 递归调用时会有一定的栈空间占用,但在这里递归的深度不会太大,因此不会显著影响总体空间复杂度。
  3. 综合考虑,总体空间复杂度为​O(n)

用键盘敲击出的不只是字符,更是一段段生活的剪影、一个个心底的梦想。希望我的文字能像一束光,在您阅读的瞬间,照亮某个角落,带来一丝温暖与共鸣。

ccxk

站长

不具版权性
不具时效性

文章内容不具时效性。若文章内容有错误之处,请您批评指正。


目录

欢迎来到ccxk的站点,为您导航全站动态

26 文章数
5 分类数
6 评论数
15标签数
最近评论
ccxk

ccxk


hi

ccxk

ccxk


Hi

Camelaaa_

Camelaaa_


热门文章

访问统计