POJ 1449 & ZOJ 1036 Enigma(简单枚举)
本文纯属原创,转载请注明出处。谢谢。
题目传送门:http://poj.org/problem?id=1449
Time Limit: 1000MS | Memory Limit: 10000K | |
Description
Figure 4: An Enigma machine (picture source: http://www.nsa.gov/museum/enigma.html).
The Enigma was a rotor machine, a cipher method which was popular at that time. A rotor is an insulated disk on which electrical contacts, one for each letter of the alphabet, are placed uniformly around the periphery and on each side. An internal conduction path through the insulating material connects contacts in pairs, one on each side of the disk. An electric current entering on one side travels on an internal path through the rotor cross-section, emerging at one of the contacts on the other side (see Figure 5 for a 3D visualisation of two rotors). Figure 6 shows a schematic side view of the complete rotor system. It shows that the Enigma has three rotors π0, π2 plus an additional reflecting rotor πR.
The input to Enigma is a stream of alphabetic characters without blanks. Every character is subject to the following steps:
1. The plaintext is subject to an initial permutation IP which is implemented by a plugboard.
2. The character resulting from step 1 is sent through the three rotors π0, π2.
3. The resulting character is then sent through the reflecting rotor πR.
4. The character from step 3 is passed back through the rotors π2, π0 (i.e., in the opposite direction).
5. The character from step 4 is subject to the inverse IP-1 of the initial permutation IP.
The interesting point about the use of rotors is that after processing each character, every rotor might be rotated by a certain angle (i.e., a certain amount of letters) before processing the next character. With the Enigma, rotor π0 is rotated by one in anti-clockwise direction with every new character. When π0 has finished one round (i.e., after processing 26 characters), rotor π1 moves by one character. Similarly, rotor π2 is rotated by one character when π1 has finished one revolution, and the reflecting rotor πR moves when π2 has finished its rotation. Obviously, πR is the slowest of the four rotors.
The process described above can be used both for encryption and decryption, provided that the permutation πR implemented by the reflecting rotor is an involution. That means πR = πR-1, or, equivalently, ξ=πR(ζ) whenever ζ=πR(ξ). You may assume that this condition holds.
The secret key of the Enigma consists of (1) the rotors π0, π2, and πR, (2) the plugboard permutation IP, and (3) the initial rotational displacements k0, k1, k2, kR of π0, π2, and πR (see below). The rotors were changed infrequently and were selected from a set of four possible rotors in the Wehrmacht model.
Problem
You are time-warped to Bletchley Park together with your laptop and should help to decipher some messages which have been intercepted over the day. You are given the entire ciphertext, parts of the plaintext, and parts of the Enigma key. Your task is to determine the correct key and finally complete the plaintext by decoding the ciphertext.
Input
Each scenario begins with the secret key of the Enigma. The secret key is specified by 6 lines. The first four lines contain a specification of the rotors π0, π2 and πR as a sequence of lowercase alphabetic characters. Character i (1<=i<=26) gives the mapping of the i-th character of the alphabet (e.g., "bha..." means that "a" is mapped to "b", "b" is mapped to "h", "c" is mapped to "a" etc.). Physically, the sequence of characters is given in clockwise direction looking from the front of the rotor stack π0, . . . , πR. After the rotors follows a similar line giving the plugboard permutation IP. Finally, the sixth line of the key gives the initial displacement k0, k1, k2, kR of the four rotors π0, π2, and πR as a string of four characters where "a" means that the rotor is in its original position (as defined by the rotor specification above), "b" means that it is rotated by one position in the usual way etc. For example, "dgaa" means that rotor π0 has initial displacement 3, π1 has 6, and π2, πR are both in their original position.
After the key follow two lines, each containing at least 1 and at most 80 lowercase letters, and no other characters. The first line contains the plaintext while the second line contains the ciphertext. The plaintext and any part of the key may be incomplete, i.e., some positions in the strings may be question marks "?". The number of question marks in the input will be at most 3.
Output
Sample Input
2 wfbtiznuvcqejpokshxgmadyrl hmrgnqpkjcaivwluebfzsyxtdo druahlbfzvgmwckxpiqysontje owtvskypjifmluahrqecndbzgx ?bcdefghijklmnopqrstuvwxyz aaaa manyorganizationsrelyoncom??
ters grsuztldsznkwnerdpfbovvqnobkyiqn oqzunvhtxwryfebicmjpklsgda zupogrskynxtwdfqvbliejcmha kzvlyjuodmscewxtfbphriqgna gbcnylaztwkfmdspqvoiurjxeh rfyhkxbuvplgtqmdiewjosznca dmeo ???
ave
Sample Output
Scenario #1: manyorganizationsrelyoncomputers Scenario #2: acm
这道题题目长的我这样的英语不好的愣是看了半天。
这是2001年XX欧现场赛的题,题目又臭又长。关键事实上就是一个十足的水题。关键是网上临时还找不到这个题的题解,仅仅有赵端阳写的《acm国际大学生程序设计竞赛题解1》里面有提到这道题,可是解说不够具体,没有解释这个机器的工作原理,所以看了一天多才看懂。然后就毅然决定写一篇来好好解释一下。
大意是这种:(算了我还是先解释一下这个机器)
首先Enigma作为德国二战时期御用的加密解密器。这个题完全然全地还原了这台天才之作的结构。
它由一个置换盘。3个置换转子盘,和一个反射转子盘组成。
首先是置换盘是一个简单的替换加密器,正向通过时A替换成X。反向通过时X就替换成A,它是固定的,也是最基础最简单的加密方法。
《福尔摩斯探案集》里面《跳舞的小人》一节就是用的这种方法。简单地来说就是用26个新的符号替换原来的26个字母。尽管这里替换的26个符号也是取自26个字母。可是它们替换之后就不代表原来的意思了。而反过来就是将26个字符相应替换回原来的字母。
接着最难理解的是3个置换转子,这也是这个机器的精髓。
昨天没有看到电路图的时候我一直在想怎么实现的,后来看了一眼电路图瞬间明确了。
以下放图。
这是一个置换转子盘的工作原理图。
我们能够看到,不管是明文(左盘)还是密文(右盘),都是按顺序排列并且不变的,变的是中间的转子电路圈。而我们能够看到。置换电路圈的电路是不变的,那么假设一開始A连到D,那么代表的是连在A点的这个电路是往下连3个的,那么一旦转子往下转动一个,不是B连接到了D点,而是B点得到了原来在A点的电路。那么B就会连到往下3个的那个地方,反之亦然。
那么每一个盘在初始状态的连接情况。就代表着这个盘26个点的电路连接状况,也就是密钥。
假设理解了这个工作原理。那么最后一个反射转子盘就很好理解了,无非就是去掉了这个转子的右盘。而把左盘上的点两两连在一起,构成了13对,当然转动也会带来配对的改变。
而这四个能够转的盘什么时候转动呢?规则是这种:每处理完一个字符,第1个转子盘逆时针转一个字符,假设第1个转子盘转了26下,也就是处理完26个字符,第二个转子盘逆时针转动一个字符,然后第二个转26下第三个就转一个字符,第四个亦然。当然我们最開始能够把每一个盘都拨动若干个字符。这个拨动不算转动次数。
以上就是对于这个机器的工作原理的解释,也是这个题最核心的地方,仅仅要弄懂了这一点,这个题就是水题了。
题意:如今给你这么一个机器。首先告诉你四个盘的密钥(它对于每条密钥,是按每行26个字符给的,第x行第i个字符代表着字母‘a’+i-1在初始状态下通过第x个盘应该变成什么)。接着第五行告诉你那个固定的简单置换盘的相应关系(给的方式同上),接下来第六行给你一行四个字符,第i个字符表示初始状态下。第i个盘被拨动了c[i]-‘a’个字符。接下来第七行给你了一段明文,第八行给你了这段明文在这个机器如上的状态下被加密之后的密文。
然后如今告诉你,这上面八行字符串里面有至多3个字符被弄得看不清了,所以读入的时候用‘?’取代了,那么请你找出这三个‘?’应该相应的正确字符后,输出完整的明文(题目保证每组測试数据有且仅有一解)。
那么这个密文在题目中的执行顺序是什么呢:
1、每一个字符首先正向通过简单置换板
2、通过步骤1得到的字符再依次正向通过1、2、3这三个转子置换板
3、通过步骤2得到的字符再通过反射转子置换板
4、通过步骤3得到的字符再依次逆向通过3、2、1这三个转子置换板
5、通过步骤4得到的字符再逆向通过简单置换板得到明文
6、每一个字符完毕上述五步之后,各个转子转动,为下一个字符的处理做准备。
思路:拿到这个题首先分析,仅仅有3个字符不确定,并且每一个之后26种情况,所以总共仅仅有26*26*26=17526种可能性,全然能够枚举出来然后一一检測能否满足解密过程。
于是就愉快地用dfs枚举然后检測了。
以下贴代码。
#include <cstdio> #include <cmath> #include <cstring> #include <string> #include <map> #include <vector> #include <iostream> #include <algorithm> #define moo 1000000007//10^9+7 #define PI acos(-1.0) using namespace std; char s[10][100];//存最原始的8行字符串 int a[10][100];//存处理后的8行字符串,处理方法是对每一个字符-'a' struct question { int x; int y; }que[5];//存问号在的位置,第X行第Y个字符 int cou;//存问号的数量 int cheek()//检測函数,对于当前枚举的情况,假设解密正确则返回1,否则返回0 { int b1[100]; int b2[100];//将明文密文取出,由于要做改动。所以避免对原数据造成影响。int len=strlen(s[7]); for(int i=0;i<len;i++) { b1[i]=a[4][a[7][i]];//将密文第一次通过简单置换板的操作顺手做了 b2[i]=a[6][i]; } int d[4][30];//存每一个转子正向通过时的电路图 int e[4][30];//存每一个转子逆向通过时的电路图 for(int i=0;i<4;i++) { for(int j=0;j<26;j++) { int t=a[i][j]; d[i][j]=t-j;//正向通过。从j变成t,电路是+(t-j) e[i][t]=j-t;//逆向通过,从t变成j,电路是+(j-t) } } int c[4];//存的是每一个转子置换板当前的转动情况。 for(int i=0;i<4;i++) c[i]=a[5][i]; for(int i=0;i<len;i++) { for(int j=0;j<=3;j++) { b1[i]+=d[j][(b1[i]+c[j]+26)%26]; b1[i]=(b1[i]+26)%26; }//当前字符依次正向通过1、2、3这三个板,由于反射板工作原理与普通的没什么两样,所以也顺手正向通过 for(int j=2;j>=0;j--) { b1[i]+=e[j][(b1[i]+c[j]+26)%26]; b1[i]=(b1[i]+26)%26; }//然后再逆向通过3、2、1这三个板。 for(int j=0;j<26;j++)//推断这个字符逆向通过简单置换板之后是不是得到明文。假设不是,则当前枚举错误 if(b1[i]==a[4][j]&&b2[i]!=j) return 0; c[0]++;//对于每一个转子做对应转动。为处理下一个字符做准备 if(c[0]==a[5][0]+26) { c[0]=a[5][0]; c[1]++; if(c[1]==a[5][1]+26) { c[1]=a[5][1]; c[2]++; if(c[2]==a[5][2]+26) { c[2]=a[5][2]; c[3]++; if(c[3]==a[5][3]+26) c[3]=a[5][3]; } } } } return 1; } int dfs(int x) { if(x==cou) { if(cheek()==1) return 1; return 0; } for(int i=0;i<26;i++)//枚举每一位填'a'+i的情况 { a[que[x+1].x][que[x+1].y]=i; if(dfs(x+1)==1) return 1; } return 0; } void init() {//预处理八行字符串,把全部点都处理成int型,方便操作,而且把'?'都提取出来 for(int i=0;i<8;i++) { int len=strlen(s[i]); for(int j=0;j<len;j++) { if(s[i][j]=='?
') { cou++; que[cou].x=i; que[cou].y=j; } else a[i][j]=s[i][j]-'a'; } } } int main() { int T; cin>>T; int dd=T; while(T--) { for(int i=0;i<8;i++) scanf("%s",s[i]); cou=0; init(); dfs(0); printf("Scenario #%d:\n",dd-T); int len=strlen(s[6]); for(int i=0;i<len;i++) printf("%c",a[6][i]+'a'); printf("\n\n"); } return 0; }