您的位置:首页 > 其它

浙大ZOJ 1009 Enigma问题解决及别人的解决方案

2014-01-06 17:16 579 查看
教训:在这题上浪费太多次机会了,因为以下几个原因:

1. 没考虑到m(m为轮子字母表的规模)为1的情况,从而导致出现“Floating Point Error”。通过将“if(i!=0&&i%(m-1)==0)”修改为“if((i+1)%m==0)”解决。

2. 在访问vector数组时,没注意到下标的界限,导致“Runtime Error”。通过增加语句“if(alpha1Current>=m)
alpha1Current=0;”解决。

3. 显示的错误。这个最头痛,浪费了3次机会。错误的原因是自己在审题时没注意“Insert a blank line between test cases”这句话,然后发现后,又急急忙忙改,导致重复出错。这个是最不应该的!所谓“之间”,就是你不能在第1个之前打印空行,也不能在最后一行后打印空行。

好了,上代码了。

一、工程代码及算法设计注释

--------------------------------------------------enigma.h----------------------------------------------
#ifndef JLU_CCST_ENIGMA_H
#define JLU_CCST_ENIGMA_H
#include <string>
#include <vector>

extern std::string deocdeEnigma(std::string alphabet1,std::string alphabet2,std::string alphabet3,
	std::vector<int> alphabet1Dis,std::vector<int> alphabet2Dis,std::vector<int> alphabet3Dis,int m,std::string ciphertext);
extern void testDeocdeEnigma();

#endif//JLU_CCST_ENIGMA_H


--------------------------------------------------enigma.cpp----------------------------------------------

/**
题目来源:浙大ACM在线测试——ZOJ,题目编号1009,题名"Enigma"
URL:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=1009
Author:hellogdc<gdcjlu@163.com>
Finish Time:2014.01.06
*/

/**
分析:
从题目的描述可知,轮子的关键在于初始状态,由这个初始状态可以得到一个转换数值序列,
然后每次转动时,都是将这个序列循环移一位。比如轮子1的初始状态为[BADFEC],由此可知
转换数值序列为[1,-1,1,2,0,-3],当加密一个字母后,该序列循环移一位,变为[-3,1,-1,1,2,0],
由此可知此时的转换字母表为[DCBEAF]。

算法设计:
根据每个密文字符,查字母表,得到对应的明文字符。
假设每个轮子的字母表规模为m。
1. 加密过程:
3个轮子对应3个字母表,首先根据第1个轮子的字母表,将明文字符x转换为y,然后在根据
第2个轮子的字母表将字符y转换为z,最后根据第3个轮子的字母表将字符z转换为最终的密文
字符w。
更新第1个轮子状态,如果(i%m==0),则更新第2个轮子状态,如果(i%(m*m)==0),
则更新第3个轮子状态。
2. 解密过程:
根据密文字符w,查第3个轮子的字母表,得到解密字符z,然后查询第2个轮子的字母表,将z
转换为字符y,然后在查询第1个轮子的字母表,将字符y转换为明文字符x。
更新第1个轮子状态,如果(i%m==0),则更新第2个轮子状态,如果(i%(m*m)==0),
则更新第3个轮子状态。
*/
#include <iostream>
#include <vector>
#include <string>
using namespace std;

/**
alphabet1:第1个轮子的字母表
alphabet2:第2个轮子的字母表
alphabet3:第3个轮子的字母表
alphabet1Dis:第1个轮子的“转换距离表”,每次都需要依据这个表来生成新的字母表
alphabet2Dis:第2个轮子的“转换距离表”
alphabet3Dis:第3个轮子的“转换距离表”
m:轮子字母表的规模
ciphercode:密文
返回:解密的明文字符串
*/
string deocdeEnigma(string alphabet1,string alphabet2,string alphabet3,vector<int> alphabet1Dis,
	vector<int> alphabet2Dis,vector<int> alphabet3Dis,int m,string ciphertext){
		string plaintext(ciphertext.length(),' ');
		//保存3个距离表的当前“头位置”
		int alpha1Current=0;
		int alpha2Current=0;
		int alpha3Current=0;
		for(int i=0;i<ciphertext.length();i++){
			int j=0;
			char ch=ciphertext[i];
			//根据字母表3进行转换
			for(j=0;j<alphabet3.size();j++)
				if(alphabet3.at(j)==ch)
					break;
			ch='A'+j;
			//根据字母表2进行转换
			for(j=0;j<alphabet2.size();j++)
				if(alphabet2.at(j)==ch)
					break;
			ch='A'+j;
			//根据字母表1进行转换
			for(j=0;j<alphabet1.size();j++)
				if(alphabet1.at(j)==ch)
					break;

			plaintext[i]='a'+j;//得到明文字符,都是小写字母

			//更新字母表
			alpha1Current++;
			for(j=0;j<m;j++)//字母表1
				alphabet1.at(j)='A'+(j+alphabet1Dis.at((m+j-alpha1Current)%m)+m)%m;//因为可能会出现负数,所以加上m
			if(alpha1Current>=m)
				alpha1Current=0;
			if((i+1)%m==0){//字母表2
				alpha2Current++;
				for(j=0;j<m;j++)
					alphabet2.at(j)='A'+(j+alphabet2Dis.at((m+j-alpha2Current)%m)+m)%m;
				if(alpha2Current>=m)
					alpha2Current=0;
			}
			if((i+1)%(m*m)==0){//字母表3
				alpha3Current++;
				for(j=0;j<m;j++)
					alphabet3.at(j)='A'+(j+alphabet3Dis.at((m+j-alpha3Current)%m)+m)%m;
				if(alpha3Current>=m)
					alpha3Current=0;
			}
		}
		return plaintext;
}

void testDeocdeEnigma(){
	int m=6;	
	string alpha1="BADFEC";
	string alpha2="ABCDEF";
	string alpha3="ABCDEF";
	vector<int> dis1(m,0);
	vector<int> dis2(m,0);
	vector<int> dis3(m,0);
	for(int i=0;i<m;i++){
		dis1.at(i)=alpha1[i]-('A'+i);
		dis2.at(i)=alpha2[i]-('A'+i);
		dis3.at(i)=alpha3[i]-('A'+i);
	}
	string ciphertext="ACE";
	string plaintext=deocdeEnigma(alpha1,alpha2,alpha3,dis1,dis2,dis3,m,ciphertext);
	cout<<"Ciphertext :"<<ciphertext<<endl;
	cout<<"Plaintext :"<<plaintext<<endl;

}


--------------------------------------------------main.cpp----------------------------------------------

#if 1
#include "enigma.h"

int main(){
	testDeocdeEnigma();
	return 0;
}
#endif


二、提交并被ZOJ成功接受的代码——算法核心代码

--------------------------------------------------submit_main.cpp----------------------------------------------
#if 1

#include <iostream>
#include <vector>
#include <string>
using namespace std;
string deocde(string alphabet1,string alphabet2,string alphabet3,vector<int> alphabet1Dis,
	vector<int> alphabet2Dis,vector<int> alphabet3Dis,int m,string ciphertext){
		if(m<=0)
			return "";
		string plaintext(ciphertext.length(),' ');
		int alpha1Current=0;
		int alpha2Current=0;
		int alpha3Current=0;
		for(int i=0;i<ciphertext.length();i++){
			int j=0;
			char ch=ciphertext[i];
			for(j=0;j<alphabet3.size();j++)
				if(alphabet3.at(j)==ch)
					break;
			ch='A'+j;
			for(j=0;j<alphabet2.size();j++)
				if(alphabet2.at(j)==ch)
					break;
			ch='A'+j;
			for(j=0;j<alphabet1.size();j++)
				if(alphabet1.at(j)==ch)
					break;
			plaintext[i]='a'+j;
			alpha1Current++;
			for(j=0;j<m;j++)
				alphabet1.at(j)='A'+(j+alphabet1Dis.at((m+j-alpha1Current)%m)+m)%m;
			if(alpha1Current>=m)
				alpha1Current=0;
			if((i+1)%m==0){
				alpha2Current++;
				for(j=0;j<m;j++)
					alphabet2.at(j)='A'+(j+alphabet2Dis.at((m+j-alpha2Current)%m)+m)%m;
				if(alpha2Current>=m)
					alpha2Current=0;
			}
			if((i+1)%(m*m)==0){
				alpha3Current++;
				for(j=0;j<m;j++)
					alphabet3.at(j)='A'+(j+alphabet3Dis.at((m+j-alpha3Current)%m)+m)%m;
				if(alpha3Current>=m)
					alpha3Current=0;
			}
		}
		return plaintext;
}

int main(){
	int m=6;
	string alpha1="";
	string alpha2="";
	string alpha3="";
	int no=1;
	while(cin>>m){
		if(m==0)
			break;
		cin>>alpha1;
		cin>>alpha2;
		cin>>alpha3;
		vector<int> dis1(m,0);
		vector<int> dis2(m,0);
		vector<int> dis3(m,0);
		for(int i=0;i<m;i++){
			dis1.at(i)=alpha1[i]-('A'+i);
			dis2.at(i)=alpha2[i]-('A'+i);
			dis3.at(i)=alpha3[i]-('A'+i);
		}
		int m_c;
		cin>>m_c;
		vector<string> ciphertexts(m_c,"");
		vector<string> plaintexts(m_c,"");
		for(int i=0;i<m_c;i++)
			cin>>ciphertexts[i];
		if(no!=1)
			cout<<endl;
		cout<<"Enigma "<<(no++)<<":"<<endl;
		for(int i=0;i<m_c;i++){
			plaintexts[i]=deocde(alpha1,alpha2,alpha3,dis1,dis2,dis3,m,ciphertexts[i]);
			cout<<plaintexts[i]<<endl;
		}
	}
	return 0;
}

#endif


三、网上别人的代码

原文地址:http://www.cnblogs.com/hoodlum1980/archive/2008/10/20/1315471.html

星期天这天一口气AC了五道题,除了1009外基本都可算是简单题。

(1)1009 Enigma:

http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=1009

题目是讲解二战期间德国使用的密码机Enigma。明文通过按键,通过转盘(rotor)转换为密文。如下图所示为有1个转盘,一共有6个字母的情况,每击键一次,转盘转动一格。如果含有多个转盘,则以类似数字进制方式转动,即第一个盘转动一圈后,第二个盘转动一格,以此类推。题目要求解密含有三个转盘的密文,第一行输入m,表示键盘一共有m个字母('A','B','C',...,'A'+m-1),然后输入三行表示每个转盘的初始字符映射状态(例如下图中的rotor的初始状态是BADFEC)。然后输入n行密文,要求输出每个密文的明文。




分析上面的图,可得转盘的输入x和输出x'之间的关系是偏移关系,即x'=x+dx;因此我们把映射关系中的偏移量dx用一个数组表示:

int rotor[m]; 这个数组中的负数也可以通过加上m矫正为正数。

例如上图中的映射关系为BADFEC,用偏移量数组表示为{1, -1, 1, 2, 0, 3},

当rotor转动一格时,相当于该数组循环向右移动一格,变为{3, 1, -1, 1, 2, 0};

因此我们完全物理模拟rotor的转动过程,给出第一个版本的AC的代码如下:



/*旋转圆盘的解密问题*/

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

/*6个转盘,前3个存储的是即时状态,后3个存储的是初始状态!!*/

char rotors[6][27];

/*每个转盘的当前步进值*/

int steps[3];

/*顺时针旋转某个转盘一个步进,

index表示转盘号,m表示每个转盘一共多少个字母*/

void Rotate(char *rotor, int m)

{

int i;

char temp;

/*先转换为偏移值,有正有负*/

for(i=0; i<m; i++)

rotor[i]=rotor[i] - ('A' + i);



/*旋转*/

temp=rotor[m-1];

for(i=m-1;i>0;i--)

rotor[i]=rotor[i-1];

rotor[0]=temp;



/*复原为字符串,同时矫正负数值*/

for(i=0; i<m; i++)

rotor[i]='A' + ( (i + rotor[i] + m) % m);

}

/*整体转动一次!m为每个转盘的字符数*/

void RotateRotors(int m)

{

steps[0]++;

Rotate(rotors[0],m);

if(steps[0]==m)

{

steps[0]=0;

steps[1]++;

Rotate(rotors[1],m);

}

if(steps[1]==m)

{

steps[1]=0;

steps[2]++;

Rotate(rotors[2],m);

}

}

/*根据输出的密文,得出原文,都是大写字母*/

char GetPlainChar(const char* rotor, char c)

{

char *p=strchr(rotor, c);

return 'A'+(p-rotor);

}

/*复原到初始状态*/

void ResetRotors()

{

steps[0]=steps[1]=steps[2]=0;

/*设置圆盘的初始状态*/

strcpy(rotors[0], rotors[3]);

strcpy(rotors[1], rotors[4]);

strcpy(rotors[2], rotors[5]);

}

int main()

{

int m, n, count=1, i;

char line[1024], *s;



while(1)

{

/*读入密码数*/

gets(line);

m=atoi(line);

if(m==0)

break;



/*每个test case之间插入一个空行*/

if(count!=1) printf("\n");



printf("Enigma %d:\n", count++);

/*读入三个rotor*/

gets(rotors[3]);

gets(rotors[4]);

gets(rotors[5]);



/*读取输入的密文数*/

gets(line);

n=atoi(line);/*读取换行符*/



/*解密*/

for(i=0;i<n;i++)

{

/*设置圆盘的初始状态*/

ResetRotors();



gets(line);

s=line;

while(*s)

{

*s=GetPlainChar(rotors[2],*s);

*s=GetPlainChar(rotors[1],*s);

*s=GetPlainChar(rotors[0],*s);

*s=*s - 'A' + 'a';/*化为小写字母*/

RotateRotors(m);

s++;

}

printf("%s\n", line);

}

}

return 0;

}

上面的代码用时190ms,而该题的解排行榜的用时为20ms,30ms,40ms。可见运行时间还可以改进,我想运行时间的改进可能是主要针对常数因子的改进。因此我们考虑上面的代码中的导致效率低下的地点所在。大致可以确定是每敲打一次按键,对rotor转动时需要对数组进行如下操作:字符串->偏移值数组->数组元素转动->字符串,虽然字符串长度不大,但它的耗时属于O(n),因此我们可以把这个过程改为O(1)。即我们不实际转动数组元素,而是利用一个标记当前的totor位置的“指针”,这样rotor转动时,我们仅仅改变“指针”的值,而不需要移动数组。

为了快速求取输入,我们把上面的数组可以认为是函数f(x),我们现在把该数组改为f的反函数即f'(x)。即:

f(x): {1, -1, 1, 2, 0, 3}; (明文)abcdef -> BADFEC (密文)

f'(x): {1, -1, 3, -1, 0, 4}; (密文)ABCDEF -> bafced (明文)

这样,我们就能根据密文,直接得到明文。因此我们得到第二个版本的代码如下:



/*旋转圆盘的解密问题,改进后为50ms*/

#include <stdio.h>

#include <string.h>

/*6个转盘,前3个存储的是正向偏移值,后3个存储的是字符状态!!*/

char rotor0[27],rotor1[27],rotor2[27];

char buf0[27], buf1[27], buf2[27];

/*每个转盘的当前位置指针!,指示每个圆盘的当前起点*/

int p0,p1,p2;

/*整体顺时针转动一次!则位置向后移动一格*/

void RotateRotors(int m)

{

p0--;

if(p0==0)

{

p0=m;

p1--;



if(p1==0)

{

p1=m;

p2--;

if(p2==0) p2=m;

}

}

}

/*根据输出的密文,得出原文,都是大写字母, pointer是该rotor的指针位置*/

char GetPlainChar(const char* rotor, int m, int pointer, char c)

{

return 'A' + (c - 'A' + rotor[ (pointer+ c-'A')%m ]) % m;

}

/*把字符串换算为偏移值(全部转为正数), m为每个圆盘的字符个数*/

/*rotors[3,4,5]存储的是字符串!*/

void InitRotors(int m)

{

int i;

/*计算出反推明文的偏移数组*/

for(i=0; i<m; i++)

{

rotor0[ buf0[i]-'A' ] = (('A'+i) - buf0[i] + m)%m;

rotor1[ buf1[i]-'A' ] = (('A'+i) - buf1[i] + m)%m;

rotor2[ buf2[i]-'A' ] = (('A'+i) - buf2[i] + m)%m;

}

}

int main()

{

int m, n, count=1, i;

char line[1024], *s;

while(1)

{

/*读入密码数*/

scanf("%d", &m);

if(m==0)

break;

/*每个test case之间插入一个空行*/

if(count!=1) printf("\n");

printf("Enigma %d:\n", count++);

/*读入三个rotor*/

scanf("%s", buf0);

scanf("%s", buf1);

scanf("%s", buf2);

/*初始化Rotors[0,1,2]*/

InitRotors(m);

/*读取输入的密文数*/

scanf("%d",&n);

/*解密*/

for(i=0;i<n;i++)

{

/*设置圆盘的初始状态*/

p0=p1=p2=m;

scanf("%s", line);

s=line;

while(*s)

{

*s='A' + (*s - 'A' + rotor2[ (p2+ *s-'A')%m ]) % m;

*s='A' + (*s - 'A' + rotor1[ (p1+ *s-'A')%m ]) % m;

*s='A' + (*s - 'A' + rotor0[ (p0+ *s-'A')%m ]) % m;

*s=*s - 'A' + 'a';/*化为小写字母*/

RotateRotors(m);

s++;

}

printf("%s\n", line);

}

}

return 0;

}

版本2的运行时间为50ms,(两个版本的内存占用都是100多K,属于小空间),因此这个解无法上榜。暂时没有想到进一步提高速度的方法,因此这道题暂且就到这里了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: