POJ2159-Ancient Cipher

全解题报告索引目录 -> 【北大ACM – POJ试题分类

转载请注明出处:http://exp-blog.com

-------------------------------------------------------------------------

 

 

PS:本题稍微说一下题意(当时有点发牢骚的感觉,O(∩_∩)O哈哈~)

一种我认为是比较符合现实的解题思路,但是总是Wrong Answer

 

咋看之下确实是被题目忽悠了,一般思路都是先对置换解密,再对乱序解密,但是题目所给出的乱序码只有10个,<2, 1, 5, 4, 3, 7, 6, 10, 9, 8>,输入要求却是不大于100的字符串,这里就显示出了矛盾所在:没有10之后的乱序码根本无法解密!!

 

初看之下这道题目确实是无理取闹,但是从矛盾中同时也给予了我们提示:我们一开始的思路就是错误的!!

 

试想想,实际中对于一段密文,即使我们知道它是使用了凯撒加密(就是置换加密)乱序加密的组合,但是我们却很难得出乱序加密的“顺序”,因为它很可能是随机的!

但是凯撒加密这一环确实可以被破解出来 的,通过循环!因为凯撒加密一开始就脱离不了alphabet!而且对于每一个字母它的置换规则都是一样的,例如A→C,是A往后+2所得,那么B的加密也只能+2,B→D,在alphabet末尾的字母就要卷回alphabet开头。那么通过循环去测试A+i的数字i,也不过是26次而已!

那么乱序加密又如何破解?答案是基本没有办法,除非知道了乱序码。但是可以通过间接手段去验证。下面将叙述。

 

本题其实已经给出了凯撒加密的数字i,就是i=1,这样就省去破解凯撒加密的时间了。

输入密文和猜测的明文后,先破解密文中凯撒加密的部分,那么现在的密文和猜测的明文区别就在于顺序性!

虽然我们不知道乱序码,但是正因为不知道乱序码,我们有更加简单的方法去判定密文和明文是否一致!我们把密文和明文都视为乱序,然后分别对它们的所有字母进行排序!

现在只需要比较排序后的两段文字,就可以判定密文和猜测的明文是否一致了。

 

 

 

正确解题思路

虽然方法更简单,但是正常人很难想到会是这个思路 

不必理会凯撒加密和乱序加密,默认都是不知道解码方法。

那么唯一判断 密文 和 猜测的明文 是否一致的方法就是 把出现频数相同的字母归结为一类(而不必理会组成这一类中的各个字母类型是否一致,但是字母的数量必须一致,例如密文中ABCD都分别出现一次,把他们归为一类,明文中BNUJ也都分别出现一次,把他们也归结为一类,那么这时认为ABCD和BNUJ这两类是等价的;但若密文中ABCD都分别出现一次,而明文中出现一次的只有 KIO三个字母,那么 ABCD和KIO是不等价的),并将出现的次数进行排序,再比较两个频数序列是否相同即可(比较的是频数列,而不是字母列),相同说明可由明文串经变换得出密文串(看这口气好像是说“至于怎么变换出来就不是我们该考虑的问题了,我们管不着!”  )

 

 

/*
	Author:     Exp
	Date:       2017-11-29
	Code:       POJ 2159
	Problem:    Ancient Cipher
	URL:		http://poj.org/problem?id=2159
*/

/*
	题意分析:
	  有一明文(纯大写字母序列,长度<100),
	  对其使用 凯撒加密(替换加密) 和 乱序加密 后得到密文
	  根据这两种加密的特点,密文与明文必定等长,且同为纯大写字母序列。
	  现给定一对密文和明文,猜测它们是否配对。

	  首先需要明确这两种加密方式的特点:
	  ① 凯撒加密:令明文中所有字母均在字母表上向前/后以固定值偏移并替换
	  ② 乱序加密:给定乱序表,交换明文中每个字母的位置

	  解题思路先入为主肯定是通过某种手段另对明文加密或对密文解密,对结果字符串进行比较.
	  但是由于题目并未给出乱序表,因此此方案不可行 
	  (若单纯只有凯撒加密,是可以通过碰撞26次偏移值做逆向还原的,
	  但由于还存在乱序加密,且乱序表的长度最大为100,根据排列组合来看是不可能的)


	  为此切入点可以通过比较明文和密文在加密前后依然保有的共有特征,猜测两者是否配对:
	  ① 明文和密文等长
	  ② 乱序加密只改变了明文中字母的顺序,原本的字母并没有发生变化
	  ③ 把明文中每个字母看作一个变量,凯撒加密只改变了变量名称,该变量出现的次数没有发生变化
	  ④ 综合①②③的条件,若分别对明文和密文每个字母进行采样,分别求得每个字母的出现频次,
	     然后对频次数列排序,若明文和密文是配对的,可以得到两个完全一样的频次数列
	  ⑤ 比较频次数列会存在碰撞几率,亦即得到只是疑似解
	    (但由于题目没有给出乱序表,基本不可能得到准确解,疑似解已经足够了)
*/

#include <algorithm>
#include <iostream>
using namespace std;


const static int MAX_LEN = 101;		// 密文/明文最大长度
const static int FRE_LEN = 26;		// 频率数组长度(记录每个字母的出现次数)


/* 
 * 计算字符串中每个字母出现的频次
 * @param _in_txt 入参:纯大写字母的字符数组
 * @param _out_frequency 出参:长度为26的每个字母出现的频次数组
 */
void countFrequency(char* _in_txt, int* _out_frequency);

/* 
 * 比对两个频次数组是否完全一致(频次数组定长为26)
 * @param aFrequency 频次数组a
 * @param bFrequency 频次数组b
 * return true:完全一致; false:存在差异
 */
bool isSame(int* aFrequency, int* bFrequency);


int main(void) {
	char cipherTxt[MAX_LEN] = { '\0' };		// 密文
	char plaintTxt[MAX_LEN] = { '\0' };		// 明文
	int cFrequency[FRE_LEN] = { 0 };		// 密文频次数列
	int pFrequency[FRE_LEN] = { 0 };		// 明文频次数列

	cin >> cipherTxt >> plaintTxt;
	countFrequency(cipherTxt, cFrequency);
	countFrequency(plaintTxt, pFrequency);
	cout << (isSame(cFrequency, pFrequency) ? "YES" : "NO") << endl; 

	//system("pause");
	return 0;
}


void countFrequency(char* _in_txt, int* _out_frequency) {
	for(int i = 0; *(_in_txt + i) != '\0'; i++) {
		*(_out_frequency + (*(_in_txt + i) - 'A')) += 1;
	}
	sort(_out_frequency, _out_frequency + FRE_LEN);
}

bool isSame(int* aFrequency, int* bFrequency) {
	bool isSame = true;
	for(int i = 0; i < FRE_LEN; i++) {
		isSame = (*(aFrequency + i) == *(bFrequency + i));
		if(isSame == false) {
			break;
		}
	}
	return isSame;
}

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值