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

教训:在这题上浪费太多次机会了,因为以下几个原因:

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=*- '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' + (*- 'A' + rotor2[ (p2+ *s-'A')%m ]) % m;
                
*s='A' + (*- 'A' + rotor1[ (p1+ *s-'A')%m ]) % m;
                
*s='A' + (*- 'A' + rotor0[ (p0+ *s-'A')%m ]) % m;
                
*s=*- 'A' + 'a';/*化为小写字母*/
                RotateRotors(m);
                s
++;
            }
            printf(
"%s\n", line);
        }
    }
    
return 0;
}

      版本2的运行时间为50ms,(两个版本的内存占用都是100多K,属于小空间),因此这个解无法上榜。暂时没有想到进一步提高速度的方法,因此这道题暂且就到这里了。




  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值