拨钟问题-枚举

OpenJudge-NOI/2.1基本算法之枚举-1816:拨钟问题

1816:拨钟问题

总时间限制: 1000ms 内存限制: 65536kB

描述

有9个时钟,排成一个3*3的矩阵。

|-------|    |-------|    |-------|
|       |    |       |    |   |   |
|---O   |    |---O   |    |   O   |
|       |    |       |    |       |
|-------|    |-------|    |-------|
    A            B            C    

|-------|    |-------|    |-------|
|       |    |       |    |       |
|   O   |    |   O   |    |   O   |
|   |   |    |   |   |    |   |   |
|-------|    |-------|    |-------|
    D            E            F    

|-------|    |-------|    |-------|
|       |    |       |    |       |
|   O   |    |   O---|    |   O   |
|   |   |    |       |    |   |   |
|-------|    |-------|    |-------|
    G            H            I    
              (1)

现在需要用最少的移动,将9个时钟的指针都拨到12点的位置。共允许有9种不同的移动。如下表所示,每个移动会将若干个时钟的指针沿顺时针方向拨动90度。

移动    影响的时钟
 
 1         ABDE
 2         ABC
 3         BCEF
 4         ADG
 5         BDEFH
 6         CFI
 7         DEGH
 8         GHI
 9         EFHI    

输入

9个整数,表示各时钟指针的起始位置,相邻两个整数之间用单个空格隔开。其中,0=12点、1=3点、2=6点、3=9点。

输出

输出一个最短的移动序列,使得9个时钟的指针都指向12点。按照移动的序号从小到大输出结果。相邻两个整数之间用单个空格隔开。

样例输入

3 3 0 
2 2 2 
2 1 2 

样例输出

4 5 8 9 

题解

由题可知,一共有9种不同的移动,那么我们将每一种移动对应于一种操作(operation),简写成op。每种操作都会影响若干个时钟,那么我们将每种操作影响的时钟转换为每个时钟受哪些操作的影响。如下所示:

A 1 2 4
B 1 2 3 5
C 2 3 6
D 1 4 5 7
E 1 3 5 7 9
F 3 5 6 9
G 4 7 8
H 5 7 8 9
I 6 8 9

又因为每次操作会将若干个时钟转动90度,所以当同一个操作进行4次,其实就是旋转了360度,相当于没有操作。(一种操作做四次和不做是一样的)所以每种操作做的次数为:0,1,2,3。一共有9种操作,所以枚举次数为4的9次方,暴力枚举就是一个9重循环。最后用一个变量sum来累加每个时钟被操作后的指向,如果累加完后sum=0,那么说明找到了一个移动序列使得9个时钟的指针都指向12点。但此时我们并不知道找到的是否就是最短的序列,所以还要进行比较取操作次数的最小值。

方法一 暴力枚举

代码如下:

#include<iostream>
using namespace std;
int ori[10];
int op[10];
int result[10];
int main()
{
	int sum,min=28,moves;
	for(int i=1; i<10; ++i)
	  cin >> ori[i];
	for(op[1]=0; op[1]<4; ++op[1])
	  for(op[2]=0; op[2]<4; ++op[2])
	    for(op[3]=0; op[3]<4; ++op[3])
	      for(op[4]=0; op[4]<4; ++op[4])
	        for(op[5]=0; op[5]<4; ++op[5])
	          for(op[6]=0; op[6]<4; ++op[6])
	            for(op[7]=0; op[7]<4; ++op[7])
	              for(op[8]=0; op[8]<4; ++op[8])
	                for(op[9]=0; op[9]<4; ++op[9])
					{
					    sum=0;
					    sum+=(ori[1]+op[1]+op[2]+op[4])%4;
					    sum+=(ori[2]+op[1]+op[2]+op[3]+op[5])%4;
					    sum+=(ori[3]+op[2]+op[3]+op[6])%4;
					    sum+=(ori[4]+op[1]+op[4]+op[5]+op[7])%4;
					    sum+=(ori[5]+op[1]+op[3]+op[5]+op[7]+op[9])%4;
					    sum+=(ori[6]+op[3]+op[5]+op[6]+op[9])%4;
					    sum+=(ori[7]+op[4]+op[7]+op[8])%4;
					    sum+=(ori[8]+op[5]+op[7]+op[8]+op[9])%4;
					    sum+=(ori[9]+op[6]+op[8]+op[9])%4;
					    if(sum==0)
					    {	
					    	moves=0;
					    	for(int i=1; i<10; ++i)
					    		moves+=op[i];
							if(moves<min)
							{
								min=moves;
								for(int i=1; i<10; ++i)
									result[i]=op[i];
							}	
					    }
					}
	for(int i=1; i<10; ++i)
	{
		while(result[i]--)
		{
			cout << i << " ";
		}
	}
	return 0;
}

枚举的核心问题就是:
1.怎样去枚举?
2.在答案正确的情况下,怎么减少枚举次数?
所以这道题怎么减少枚举次数呢?
枚举中有一个十分重要的思想—局部思想法,它的基本思路如下:
如果存在某个局部, 一旦这个局部的状态被确定, 那么剩余其他部分的状态只能是确定的一种, 或者不多的n 种, 那么就只需枚举这个局部的状态即可。
所以我们来看这道题,看能不能找到一个局部使得枚举的次数减少。不难发现操作1,2,3为一个局部。例如,当我们确定了操作1,2,3的次数以后,得到A,B,C这三个时钟的指针的指向,此时只有操作4能够改变A时钟的的指针方向,使它能指向12点。同理,只有操作5才能够改变B时钟的指针方向,只有操作6才能够改变C时钟的指针方向。那么操作4-6的次数也就确定了。同样,继续往下,现在操作1-6的次数都确定了,只有操作7才能改变D时钟的指针方向,只有操作8才能改变G时钟的指针方向,只有操作9才能改变F时钟的指针方向。
这样操作1-9的次数都被确定了,A,B,C,D,G,F这6个时钟的指针都指向了12点,只有E,H,I这三个时钟指针方向还没有确定。所以剩下只用判断E,H,I这三个时钟的指针指向12点没有,如果都指向了12点,那说明找到了一个序列使得9个时钟的指针都指向了12点。
所以根据局部思想的方法,我们只需对操作1,2,3进行枚举,枚举的次数为4的3次方=64次。

方法二 局部枚举

代码如下:

#include<iostream>
using namespace std;
int ori[10];
int op[10];
int result[10];
int main()
{
	int min=28,moves,e,h,i;
	for(int i=1; i<10; ++i)
	  cin >> ori[i];
	for(op[1]=0; op[1]<4; ++op[1])
	  for(op[2]=0; op[2]<4; ++op[2])
	    for(op[3]=0; op[3]<4; ++op[3])
		{
			op[4]=(4-(ori[1]+op[1]+op[2])%4)%4;//确定A
			op[5]=(4-(ori[2]+op[1]+op[2]+op[3])%4)%4;//确定B
			op[6]=(4-(ori[3]+op[2]+op[3])%4)%4;//确定C
			op[7]=(4-(ori[4]+op[1]+op[4]+op[5])%4)%4;//确定D
			op[8]=(4-(ori[7]+op[4]+op[7])%4)%4;//确定G
			op[9]=(4-(ori[6]+op[3]+op[5]+op[6])%4)%4;//确定F
			e=(ori[5]+op[1]+op[3]+op[5]+op[7]+op[9])%4;
			h=(ori[8]+op[5]+op[7]+op[8]+op[9])%4;
			i=(ori[9]+op[6]+op[8]+op[9])%4;
			if((e+h+i)==0)//判断E,H,I这三个时钟指针都指向12点没有
			{
				moves=0;
				for(int i=1; i<10; ++i)
					moves+=op[i];
				if(moves<min)
				{
					min=moves;
					for(int i=1; i<10; ++i)
						result[i]=op[i];
				}		
			}
		}
	for(int i=1; i<10; ++i)
	{
		while(result[i]--)
		{
			cout << i << " ";
		}
	}
	return 0;			    
}

感谢观看!如有错误,还请指出。
此题来源于http://noi.openjudge.cn/ch0201/1816/
同时我也向大家推荐中国大学MOOC上的来自北京大学郭炜老师的课程—程序设计与算法(二)算法基础。
只狼

<think>好的,用户想设计一个使用74LS147、74LS175、74LS161和NE555芯片的6位密码数字电路锁的原理图。我需要先理解这些芯片的功能以及如何将它们组合起来实现密码锁的功能。 首先,74LS147是10线-4线优先编码器,可以将输入的数字编码成BCD码。密码锁可能需要输入数字,所以这个芯片可能用于处理按键输入。不过通常密码锁需要的是将按键对应到数字,而74LS147是优先编码器,可能用于处理多个按键输入时的优先级,但如果是单键输入的话可能需要其他方式。或者用户可能希望用矩阵键盘,但这里提到的芯片中没有驱动矩阵的,可能需要其他处理。 接下来是74LS175,这是一个四D触发器,可以用来存储数据。密码锁需要存储用户设置的密码和输入的密码进行比较,所以这部分可能用于存储密码位。每个D触发器可以存储一位,但需要多个芯片来存储6位密码。可能需要两片74LS175来存储6位,因为每片有四个触发器,但可能需要级联或使用其他方式。 然后是74LS161,这是4位二进制同步计数器,可以用作分频器或定时器。在密码锁中,可能用于生成时信号,或者作为输入次数计数器,比如限制输入次数,超过次数后锁定。NE555是定时器芯片,常用于产生时脉冲或延时,可能用来生成时信号给计数器或者其他时序电路。 接下来需要确定整个系统的工作流程。用户输入6位密码,通过编码器转换成BCD码,然后存储到触发器中,与预设的密码进行比较。比较部分可能需要异或门或者其他逻辑电路来判断是否匹配。如果匹配,则开锁;否则可能有错误提示或计数器记录尝试次数。 可能的模块划分: 1. 输入模块:使用74LS147将按键输入编码为BCD码。但注意74LS147是优先编码器,如果有多个按键按下,只会编码最高优先级的输入,这可能不适合密码输入,因为密码可能需要依次输入每个数字。所以可能需要每个数字输入单独处理,或者使用其他方式确保每次只有一个按键按下。或者用户可能使用单独的按钮开关,每个数字对应一个按钮,但这样需要多个输入线。这里可能需要重新考虑输入处理方式,或者使用矩阵键盘配合其他芯片。 或者,用户可能使用码开关来设置密码,而输入部分则是通过按钮依次输入,但需要存储每个输入的数字。这部分可能需要更多的触发器或寄存器。 2. 存储模块:使用74LS175存储输入的6位密码。每片74LS175有四个D触发器,所以需要两片来存储6位,每个位对应一个触发器。需要将输入的BCD码依次存入触发器。但如何控制存储的顺序?可能需要移位寄存器,但74LS175是并行输入的,可能需要配合计数器来选择存储的位置。 3. 比较模块:比较输入的密码和预设的密码是否一致。预设密码可能需要存储在另一个寄存器中,比如使用码开关设置,然后通过异或门比较每一位是否相同。如果所有位都相同,则输出开锁信号。 4. 控制逻辑:使用74LS161作为计数器,记录输入的位数。当输入6位后,触发比较操作。同时,NE555可能用于生成时信号,控制输入的有效时间或作为计数器的时源。 可能的设计步骤: - 输入部分:每个数字输入通过按钮触发,经过防抖电路(可能用NE555构成单稳态触发器),然后通过74LS147编码为BCD码。但74LS147是10线-4线编码器,输入低电平有效,所以当按下按钮时,对应的输入线接地,编码器输出对应的BCD码(反码,需要加反相器)或者直接使用。例如,按钮0对应输入线0,按下时编码器输出0000(但实际74LS147的输出是反码,比如输入线0被按下时,输出是1110,对应十进制9的反码?需要查数据手册确认)。这可能比较复杂,或者用户可能使用其他编码方式。 或者,可能使用BCD开关直接设置输入,但用户提到的是6位密码,可能需要每个数字单独输入,所以需要存储每个输入的数字。例如,用户依次按下6个数字按钮,每个按钮对应一个数字,通过编码器转换为BCD码,并依次存储到寄存器中。 - 存储部分:使用两片74LS175(四D触发器)来存储6位密码。每个密码位需要4位BCD码,但这样的话,6位密码需要6x4=24位,需要六片74LS175,这显然不合理。所以可能用户的设计中,每个密码位是单一数字(0-9),用4位BCD码表示,但存储6位的话,每个位需要4位存储空间。这样存储6位需要6x4=24位,即需要6个4位寄存器,这可能不太现实。或者,用户可能采用每个密码位为十进制数字,用4位BCD存储,总共需要6x4=24位存储,这可能需要多个寄存器芯片,如74LS175每片存储4位,所以需要6个这样的芯片,但用户只提到使用74LS175,可能这里存在误解。或者,用户可能简化设计,每个密码位用4位二进制表示,但只允许0-9,但这样的话,可能需要比较复杂的比较电路。 或者,可能密码锁的每个位是二进制位,但用户需要的是数字密码,所以每个数字用BCD编码,存储为4位。这种情况下,6位密码需要6x4=24位存储。但用户提供的芯片中没有足够的寄存器,所以可能需要其他方法,比如使用移位寄存器来串行输入,但74LS175是并行寄存器,无法移位。因此可能需要重新考虑存储结构。 可能这里需要更详细的分析。用户提到的芯片包括74LS147(编码器)、74LS175(四D触发器)、74LS161(计数器)和NE555。设计思路可能是: 1. 输入部分:使用74LS147将按键输入(0-9)编码为4位BCD码。每次按下一个数字键,74LS147输出对应的BCD码(可能需要反相,因为74LS147输出的是反码)。例如,按下按键1,输入线1被拉低,编码器输出1110(对应二进制0001的反码),即实际BCD码为0001。但需要反相器将输出转换为正逻辑。 2. 输入存储:使用74LS175来存储输入的每一位。因为每个74LS175有四个D触发器,可以存储4位,即一个BCD数字。要存储6位密码,需要6个74LS175芯片,这显然不现实。或者,用户可能使用每个74LS175存储一位的BCD码,即每个数字的4位存储在一个74LS175中,但需要四D触发器来存储四位,所以每个数字需要一片74LS175。因此,6位密码需要6片74LS175,这可能不符合用户的设计需求,因为用户只提到使用这些芯片,可能设计中有更巧妙的方法。 或者,用户可能将输入的6位密码依次存储到多个触发器中,通过计数器控制存储的位置。例如,使用74LS161作为计数器,记录当前输入到第几位。每输入一个数字,计数器加一,并将当前输入的BCD码存储到对应的触发器组中。例如,当计数器值为0时,将输入存储到第一位,计数器值为1时存储到第二位,依此类推,直到第六位。但如何将数据存储到不同的触发器组需要多路选择器或解码器,这可能增加电路复杂度。 3. 预设密码存储:可能需要使用码开关或其他方式设置预设密码,每个数字的BCD码通过开关设置,然后与输入的密码进行比较。 4. 比较电路:比较输入存储的6位BCD码和预设的BCD码是否一致。这需要每个对应的4位进行比较,可以使用异或门(如74LS86)比较每一位,如果所有位都相同,则输出开锁信号。 5. 控制逻辑:使用74LS161作为计数器,控制输入的顺序和存储位置。每次按下输入键时,计数器递增,选择下一个存储位置。NE555可能用于生成时信号,或者作为按键去抖动电路的一部分。 6. 锁定机制:如果密码错误次数超过限制,可以使用另一个计数器(如74LS161)记录错误次数,当达到设定值时锁定系统,需要NE555构成定时器来在一定时间内禁止输入。 可能的原理图步骤: - 输入按键通过74LS147编码为BCD码(可能需要反相器)。 - NE555构成单稳态触发器用于按键去抖动。 - 74LS161作为输入位数计数器,从0到5,每输入一位后递增。 - 每个计数器状态对应到多路分配器,选择对应的74LS175来存储当前输入的BCD码。 - 预设密码通过码开关设置,连接到比较电路。 - 比较电路使用异或门和与门,判断每一位是否匹配。 - 当六位均匹配时,驱动开锁机构(如继电器或LED)。 - 错误计数器使用另一片74LS161,每次错误输入后递增,达到设定值后触发锁定,可能通过NE555定时器进行锁定一段时间。 但用户提到的芯片中没有多路分配器或异或门芯片,可能使用其他逻辑门(如74LS00与非门)来构建比较电路。或者可能用户的设计中简化了比较部分。 需要指出的是,用户提供的引用资料中,比如引用[1]和[2]中使用了74LS148编码器、锁存器、译码器等,而用户的问题中的芯片不同,但可以参考类似的结构。例如,使用74LS147作为输入编码,74LS175作为锁存器存储输入,74LS161作为计数器控制输入位数,NE555用于时或去抖动。 可能的原理图设计步骤: 1. 按键输入经过去抖动电路(NE555单稳态触发器)后,连接到74LS147编码器,转换为4位BCD码。 2. 74LS161作为输入位数计数器,初始为0,每输入一位后时触发,计数到5后停止。 3. 74LS175用于存储当前输入的BCD码,但需要根据计数器的值选择存储到哪一组触发器。这里可能需要多路分配器或解码器,将计数器的输出转换为选择信号,控制不同的74LS175的时输入。例如,当计数器为0时,第一个74LS175的时使能,存储第一位;计数器为1时,第二个使能,依此类推。 4. 预设密码通过另一组74LS175或码开关设置,连接到比较电路。 5. 比较电路使用异或门比较每一位的输入和预设值,所有位匹配后输出高电平。 6. 如果比较成功,驱动开锁信号;否则,错误计数器递增,超过次数后锁定。 可能遇到的挑战包括如何将6位密码存储到有限的触发器中,如何控制存储位置,以及比较电路的设计。用户可能需要在每个存储位置使用一个74LS175来存储4位BCD码,总共需要6个74LS175,这可能不太实际。或者,可能采用串行输入的方式,但需要移位寄存器,而用户提到的芯片中没有移位寄存器,只有D触发器。 或者,可能使用74LS161作为密码输入的顺序控制,每次输入的数字通过编码器存入一个暂存寄存器,然后通过控制逻辑将其存储到相应的位置。例如,当输入第一个数字时,计数器为0,将该数字存入第一个4位寄存器;输入第二个时,计数器为1,存入第二个寄存器,以此类推。 另外,NE555可能用于生成时脉冲,或者作为按键去抖动电路。例如,每个按键按下时触发NE555的单稳态电路,产生一个稳定的脉冲信号,作为编码器的输入和计数器的时信号。 综上,虽然具体原理图需要详细设计,但可以基于上述思路构建一个框架。可能需要参考类似的设计,如引用[2]中的病床呼叫系统,使用编码器、锁存器和计数器,结合NE555实现控制逻辑。</think>### 六位密码锁设计原理分析 基于74LS147、74LS175、74LS161和NE555芯片的密码锁设计可分为**输入编码、密码存储、时序控制、比较验证**四个核心模块,具体原理如下: #### 1. 输入编码模块 使用74LS147优先编码器将0-9数字按键输入转换为4位BCD码。由于74LS147输出为反码(例如按键1输入对应编码$1110_2$),需通过74LS04反相器转换为标准BCD码(如$0001_2$)。NE555构成单稳态触发器用于按键消抖,确保输入信号稳定[^2]。 #### 2. 密码存储模块 通过两片74LS175四D触发器存储6位密码(每片存储3位,共需两组)。设计时需配合74LS161计数器控制存储位置: - 74LS161构成6进制计数器,其输出端$Q_0Q_1Q_2$通过3-8线译码器(如74LS138)生成6个使能信号 - 每个使能信号对应一位密码的存储触发,例如第一组74LS175的CLK端由译码器Y0控制,存储第一位密码的BCD码 #### 3. 时序控制模块 NE555产生1Hz方波作为系统时,驱动74LS161计数器工作。每输入一位密码,计数器加1,完成6位输入后通过门电路触发比较验证[^3]。 #### 4. 比较验证模块 预设密码通过码开关设置,与存储的输入密码通过74LS86异或门逐位比较。若所有位匹配(异或输出全0),则74LS08与门输出高电平驱动开锁继电器;否则触发错误计数器(另一片74LS161)累计错误次数,超过阈值后锁定系统。 ### 参考原理图框架 ``` [按键输入] → [NE555消抖] → [74LS147编码] → [74LS04反相] ↓ [74LS161计数器] → [74LS138译码] → [74LS175存储组] ↓ [码开关预设] → [74LS86比较] → [74LS08验证] → [执行机构] ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值