DigitalCircuit
本关是一个由不同逻辑门(可能包含与非门、异或门等)构成的数字电路,包含 4 个输入端和 8 个输出端,初始输出为
00000000
。你需要通过合理输入,使得此电路能够输出
11111111
。
本关的情况有点出乎意料,因为很多人过关不是依靠真的把题解出来,而是「一不小心碰出全 1
然后就通关了」,可见题目的复杂度还有待提升。我的预期好歹也是大家要测测电路,找找规律才行。
解析
这道题对于没学过数电的人来说并不友好,因为它不是一个简单的组合逻辑电路。在组合逻辑电路中,由确定的输入只会得出确定的输出。但在本题,你只要连续使用同样的输入来观测输出,就会发现输出结果是变化的。所以说,这个电路很有可能是时序逻辑电路。
具体的找规律过程就不提了,我直接说结果。
设输入为 ,上一轮的输出为 ,本轮输出为 ,那么:
就等于你的输入,也就是说
只有在「本次输入与上次输入(上次输入的结果间接记录在了上次输出当中)完全相反」时,才会得到 1
;否则得到 0
。
组成二位计数器,也就是说
则等于所有输入 、输出 的异或和。
以下 C++ 代码可以实现由输入和上次输出推算本次输出。
std::array<bool,preset::OutputNum> operate::signal(std::array<bool,preset::InputNum> &input,std::array<bool,preset::OutputNum> &output){
std::array<bool,preset::OutputNum> result;
//result[7,5,3,1]记录了本次的输入
result[7]=input[3];
result[5]=input[2];
result[3]=input[1];
result[1]=input[0];
//result[6]只有在「本次输入与上次输入完全相反」时,才会得到true
result[6]=input[3]^output[7]&&input[2]^output[5]&&input[1]^output[3]&&input[0]^output[1];
//result[4,2]组成二位计数器
result[4]=output[4]^output[2];
result[2]=!output[2];
//result[0]等于所有输入、输出的异或和
result[0]=false;
for(int i=0;i<preset::InputNum;i++)
result[0]^=input[i];
for(int i=0;i<preset::OutputNum;i++)
result[0]^=output[i];
return result;
}
回到我们的问题。我们的初始状态是 00000000
,而我们希望到达全 11111111
,这其实就是一个在有向图上寻找路径的问题,那么无论做广度优先搜索还是求最短路都是可行的。
人工推导
当然,这题不编程也完全可以做,我们试着寻找一下思路:
就像走迷宫那样,有的时候从终点开始要比从起点开始更加容易。
要想达到 11111111
,最后一步的输入必定要是 1111
,因为输出当中就有四位数等于输入。
而我们又要保证 等于 1
,那么前一次的输入必须是 0000
才行。
同时, 构成二位计数器,也就是说,在前一回合,它们的输出将是 。
接下来的流程也可以照猫画虎地做,不过分岔会越来越多,很难逐个分析;因此我建议能编程的玩家还是试试编程求解一下。
编程求解
我们可以运用图论的知识建立一个有向图 ,图上的点表示「输出的结果」,图中共有 个点;边表示「经过一次输入,某个状态可以转移成另一个状态」,图中共有 条边。
我们还可以推断出这个图的一些特征,例如:因为有两位计数器每回合都会发生变化,所以任何一个状态在经历一次输入之后不可能返回它自身,因而 中没有自环。(当然这是题外话了)
接下来就可以在图上搜索或者求最短路。我用的是 Floyd 算法。
#include<array>
#include<limits>
#include"operate.hpp"
#include"preset.hpp"
void operate::floyd(
std::array<std::array<int,1<<preset::OutputNum>,1<<preset::OutputNum> &dist,
std::array<std::array<int,1<<preset::OutputNum>,1<<preset::OutputNum> &prev
){
for(int o=0;o<1<<preset::OutputNum;o++)
for(int r=0;r<1<<preset::OutputNum;r++){
dist[o][r]=std::numeric_limits<int>::max()/2;
prev[o][r]=-1;
}
for(int o=0;o<1<<preset::OutputNum;o++){
dist[o][o]=0;
prev[o][o]=o;
std::array<bool,preset::OutputNum> output;
for(int d=0;d<preset::OutputNum;d++)
output[d]=o&1<<d;
for(int i=0;i<1<<preset::InputNum;i++){
std::array<bool,preset::InputNum> input;
for(int d=0;d<preset::InputNum;d++)
input[d]=i&1<<d;
std::array<bool,preset::OutputNum> result{operate::signal(input,output)};
int r{0};
for(int d=0;d<preset::OutputNum;d++)
if(result[d])
r+=1<<d;
dist[o][r]=1;
prev[o][r]=o;
}
}
for(int k=0;k<1<<preset::OutputNum;k++)
for(int i=0;i<1<<preset::OutputNum;i++)
for(int j=0;j<1<<preset::OutputNum;j++)
if(dist[i][j]>dist[i][k]+dist[k][j]){
dist[i][j]=dist[i][k]+dist[k][j];
prev[i][j]=prev[k][j];
}
}
Floyd 算法应该是这里面最麻烦的了,不过我有不得不用的理由——那就是分析整个图的性质。
不过这些内容就是出题时需要考虑的了,如果我有空的话,之后会对此做进一步说明。