Meeting-in-the-Middle,又称“中途相遇法”。准确地说,它只是一种策略。
顺便说一下,这个算法真的很冷门!
结合这道题来讨论一下吧:LA 2965。ε(┬┬﹏┬┬)3
因为博主的英文实在是十分拙劣,所以只能给出题目大意:
题目大概是说,输入多组数据(组数未知 反正不多啦),每组给定一个$k<=24$,代表有k个字符串。
然后每组的后k行,依次输入这么多个字符串。
要求你选择尽量多的字符串,使得被选的字符串中的所有字母中,每种字母都出现偶数次。
输出选择字符串数,并从小到大输出被选字符串。
给组样例吧:
in
1 ABC 6 ABD EG GE ABE AC BCD
out
0 5 1 2 3 5 6
这个题目,乍一看上去,博主就只想到了模拟。。。(博主蒟蒻一枚)。
事实上,仔细分析,我们会发现:
这个题目,和字母的个数实际上没多大关系,更多的是和字母出现次数的奇偶有关。
于是我们假设有这么一张表:(拿样例中k=6的那一组为例)
A B C D E F G H…… 1 1 0 1 0 0 0 0 …… 0 0 0 0 1 0 1 0 …… 0 0 0 0 1 0 1 0 …… 1 1 0 0 1 0 0 0 …… 1 0 1 0 0 0 0 0 …… 0 1 1 1 0 0 0 0 ……
代表什么呢?显然这个表上的每一行都是一个26为的二进制数,而每一位对应一个字母,代表这个字母在此串中出现次数的奇或偶。
1代表的是奇,0代表的是偶。
仔细想想,我们是不是只要这么做:选择尽量多的串,使其异或值为0。
好。有了这张表,这就好办了。我们可以进行暴力穷举法$O(2^n)$。是不是还是很low?显然,这个时间复杂度过不了规模高达24的数据。
那么,我们怎么办呢?
这时候,我们开始讲到的MITM策略就可以派上用场了。
别看它的名字这么高级,其实思想主要是:
将待测数据(也就是那张表)分成两部分,枚举上表的所有子集的xor值,放入一个hash表或者map映射里。
然后枚举后半部分的数据所能得到的所有xor值,并每次都在hash表或者map映射里查找。
如果用map实现的话,总时间复杂度为$O(2^{(n/2)}logn)$,即$O(1.414^nlogn)$,比第一种方法好了很多。
这就是Meeting-in-the-Middle中途相遇法。
据刘汝佳神犇所述:密码学中的著名的中途相遇攻击(Meeting-in-the-Middle attack)就是基于这个原理。
最后贴上代码吧:
#include <bits/stdc++.h> using namespace std; const int maxn=24; map <int,int> table; int bitcount(int x) { return x==0?0:bitcount(x/2)+(x&1); }//这个函数用来计算这个数在二进制下有多少个1 int main() { int n,A[maxn]; char s[1000]; while (scanf("%d",&n)==1&&n) {//读入多组数据 for (int i=0;i<n;i++) { scanf("%s",s);//输入字符串 A[i]=0; for (int j=0;s[j]!='