【题解】古代密码

题目描述

古罗马帝国有一个拥有各种部门的强大政府组织。其中一个部门就是保密服务部门。为了保险起见,在省与省之间传递的重要文件中的大写字母是加密的。当时最流行的加密方法是替换和重新排列。

替换方法是将所有出现的字符按照一个规则替换,比如 ABCDEFGHIJKLMNOPQRSTUVWXYZ 到 BCDEFGHIJKLMNOPQRSTUVWXYZA,如果原词是 VICTORIOUS 则它变成 WJDUPSJPVT

排列方法改变原来单词中字母的顺序。例如:将顺序 (left<2,1,5,4,3,7,6,10,9,8 ight>) 应用到 VICTORIOUS 上,则得到 IVOTCIRSUO

人们很快意识到单独应用替换方法或排列方法加密,都是很不保险的。但是如果结合这两种方法,在当时就可以得到非常可靠的加密方法。所以,很多重要信息先使用替换方法加密,再将加密的结果用排列的方法加密。用两种方法结合就可以将 VICTORIOUS 加密成 JWPUDJSTVP

考古学家最近在一个石台上发现了一些信息。初看起来它们毫无意义,所以有人设想它们可能是用替换和排列的方法被加密了。人们试着解读了石台上的密码,现在他们想检查解读的是否正确。他们需要一个计算机程序来验证,你的任务就是写这个验证程序。

输入格式

输入有两行。

第一行是石台上的文字 (S)。文字中没有空格,并且只有大写英文字母。

第二行是被解读出来的加密前的文字 (T)。第二行也是由大写英文字母构成的。

输出格式

如果第二行经过某种加密方法后可以产生第一行的信息,输出 YES,否则输出 NO

数据范围

测试时间限制 (1000 mathrm{ms}),空间限制 (512 mathrm{MiB})

  • 对于 (30\%) 的数据,(|S|,|T|le 10)
  • 对于 (50\%) 的数据,(|S|,|T|le 50)
  • 对于 (100\%) 的数据,(|S|,|T|le 100)

分析

这道题的关键就是要抓住不变量来解决问题。

注:设 (n)(|S|)(|T|) 同阶。

(30 mathtt{pts})

很简单,就是枚举 (26) 种情况,然后枚举所有排列,看看有没有重合的。

复杂度:(Theta(n imes n!))

(100 mathtt{pts})

这也很简单,只要枚举 (26) 种情况,分别排序并核对即可。

复杂度:(Theta(n^2log n))

(mathcal{E}xtra mathcal{S}core)

如果此题的数据范围提升到 (nle 10^5),还有没有方法做呢?

答案是有的。关键是我们要学会抓住不变量。

注意到两个方法会对原串修改,但是这个修改是有迹可循的。

比如说,字符排列的不变量就是字符集合(这里的集合是多重集合,也就是说可以有重复元素)。

同时,按照规则替换不变的就是固定字符出现的数量。

比如,ABCDDEEE 无论怎么改,必然有一个字符出现三次,有一个出现两次,而还有三个字符各出现一次。

注意到,字符集合不变也意味着固定字符出现的数量不变。

那么,我们能不能就此判断固定字符出现次数不变就是评判标准呢?

NoNoNo,我们还要证明,对于所有的固定字符出现次数不变的情况,必然会存在一种操作方法,使得这两个字符串可以互相转化。

其实很简单。我们只要证明存在一个字符串 (A),使得 (S) 可以通过交换法则得到 (A),且 (A) 的字符集与 (T) 对应。

因为固定字符出现次数不变,我们只要对其出现次数排序则两个数列必然相同。让对应的字符转换即可。


这样,我们就能够在 (Theta(n+|U|log |U|)) 内完成问题,其中 (U) 是字符集。

Code

这里的写法用的是最后一种方法,还是比较快的。

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

const int max_n = 100, max_charset = 26;

char s1[max_n+1], s2[max_n+1];
int cnt1[max_charset] = {}, cnt2[max_charset] = {};

int main()
{
	gets(s1);
	gets(s2);
	
	for (int i = 0; s1[i]; i++)
		cnt1[s1[i]-'A']++;
	for (int i = 0; s2[i]; i++)
		cnt2[s2[i]-'A']++;
	
	sort(cnt1, cnt1 + max_charset);
	sort(cnt2, cnt2 + max_charset);
	
	for (int i = 0; i < max_charset; i++)
		if (cnt1[i] != cnt2[i])
		{
			puts("NO");
			return 0;
		}
	
	puts("YES");
	
	return 0;
}

后记

这道题很水,但是我们可以试图找到更有意思的做法来增加难度。

同时,我们也要在不确定时用证明来给出答案。

原文地址:https://www.cnblogs.com/5ab-juruo/p/solution-20200329-crytogram.html