Hash Killer I 2 3 题解(hash+构造)

Hash Killer I 题目链接

题目大意

就是出一组数据卡掉hash ull的自然溢出

题目链接

首先明白两点:

1.卡hash的关键在于构造两个不同的串对应的hash值相同。

2.爆u64相当于对2^64这个数取模。

如果base是偶数

那么a…aaa(>64个a)与ba…aa(a的数量为前面那么串a的数量-1),这两个串长度相同,hash值相同,显然串是不同的,这样就卡掉了。

如果base是奇数
说明

1:现在只考虑a、b两个字母。

2:a \ b表示a能整除b。

3:设数学上的函数not(S)表示把字符串S中每个位置的’a’变成’b’,把’b’变成’a’后形成的字符串。比如not(“ababaa”) = “bababb"strA . strB代表字符串连接。如"娃” . “哈哈” = “娃哈哈”

4:|str|表示字符串str的长度。

正文

设字符串序列{orzstr[i]},orzstr[1] = “a”, orzstr[i] = orzstr[i - 1] . not(orzstr[i - 1])
那么|orzstr[i]| = |orzstr[i - 1]| * 2。显然这是等比数列,得到:|orzstr[i]| = |orzstr[1]| . 2 ^ (i - 1) = 2 ^ (i - 1)
设hash(str)为str的哈希值。
则:
hash(orzstr[i]) = hash(orzstr[i - 1]) * base ^ |not(orzstr[i - 1])| + hash(not(orzstr[i - 1]))
= hash(orzstr[i - 1]) * base ^ (2 ^ (i - 2)) + hash(not(orzstr[i - 1]))
hash(not(orzstr[i])) = hash(not(orzstr[i - 1])) * base ^ (2 ^ (i - 2)) + hash(orzstr[i - 1])
两式相减:
hash(orzstr[i]) - hash(not(orzstr[i]))
= (hash(orzstr[i - 1]) * base ^ (2 ^ (i - 2)) + hash(not(orzstr[i - 1]))) - (hash(not(orzstr[i - 1])) * base ^ (2 ^ (i - 2)) + hash(orzstr[i - 1]))
= (hash(orzstr[i - 1]) - hash(not(orzstr[i - 1]))) * (base ^ (2 ^ (i - 2)) - 1)
这让我们发现,hash(orzstr[i]) - hash(not(orzstr[i]))似乎是个神奇的东西。而我们的目的实际上是要找两个字符串strA, strB使得
hash(strA) % 2^64 = hash(strB) % 2^64
相当与
2^64 \ hash(strA) - hash(strB)
设数列{f[i]},f[i] = hash(orzstr[i]) - hash(not(orzstr[i]))
这样就有:
f[i] = f[i - 1] * (base ^ (2 ^ (i - 2)) - 1)
还是有点不爽啊……我们再设数列{g[i]},g[i] = base ^ (2 ^ (i - 1)) - 1
于是能写成:
f[i] = f[i - 1] * g[i - 1]
则f[i] = f[1] * g[1] * g[2] * … * g[i - 1]
然后发现一个神奇的事情?
base是奇数,则base的任意正整数次方也一定是奇数。所以对于任意的i必有g[i]为偶数,所以2 ^ (i - 1) \ f[i]
问题是不是结束了呢……发现没有……这样的话我们要使2 ^ 64 \ f[i],至少得让i = 65……然后发现|orzstr[65]|是个天文数字。
发现我们刚才那样分析太坑爹了……
i > 1时有:
g[i] = base ^ (2 ^ (i - 1)) - 1 = (base ^ (2 ^ (i - 2)) - 1) * (base ^ (2 ^ (i - 2)) + 1) = g[i - 1] * 一个偶数
而g[1]显然是偶数吧……
那么4 \ g[2],8 \ g[3]…
也就是说2 ^ i \ g[i]
所以f[i] 实际上有:
(2 ^ 1) * (2 ^ 2) * (2 ^ 3) * … * (2 ^ (i - 1)) \ f[i]
2 ^ (i * (i - 1) / 2) \ f[i]
当i取12时,就有66个2了哟!
这就是卡base为奇数时的方法。orzstr[12]和not(orzstr[12])即为所求。

而读入中base既有奇数又有偶数,直接在奇数构造的字符串后面加64个a就可以了。

代码

#include <iostream>
#include <cstring>
using namespace std;
char s[10000];
int main(){
	cout<<(1<<12)+65<<" "<<(1<<11)<<endl;
//这就是卡base为奇数时的方法。orzstr[12]和not(orzstr[12])即为所求。
	int now=1;
	s[1]='a';
	for (int i=1;i<=12;i++){
		for (int j=1;j<=now;j++){
            s[now+j]=s[j]=='a'?'b':'a';//这个写法要学习一下
		}
		now<<=1;
	}
	for (int i=1;i<=now;i++)
		printf("%c",s[i]);
	for (int i=1;i<=65;i++)
		printf("a");
	printf("\n");
	return 0;
}

Hash Killer 2 题目链接

题目大意

构造数据卡掉1e9+7的hash

题目思路

生日攻击:如果你在n个数中随机选数,那么最多选sqrt(n)次就能选到相同的数

同样的,这题的Hash值在0到1000000007.那就要选差不多10^5次

唯一注意的是l要取大,使得方案数超过Mod否则就不可能有2个数有相同的Hash值

公式为
具体的神仙博客

代码

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
using namespace std;
int main(){
    printf("100000 20\n");
    for(int i = 1;i <= 100000;i++){
        printf("%c",(rand() % 26 + 'a'));
    }
    printf("\n");
    return 0;
}

Hash Killer 3 题目链接

题目思路

双hash无解

hash的总结

那么我们选择什么进制比较好?

首先不要把任意字符对应到数字0,比如假如把a对应到数字0,那么将不能只从Hash结果上区分ab和b(虽然可以额外判断字符串长度,但不把任意字符对应到数字0更加省事且没有任何副作用),一般而言,把a-z对应到数字1-26比较合适。

关于进制的选择实际上非常自由,大于所有字符对应的数字的最大值,不要含有模数的质因子(那还模什么),比如一个字符集是a到z的题目,选择27、233、19260817 都是可以的。

模数的选择(尽量还是要选择质数):

绝大多数情况下,不要选择一个1e9的质数级别的数,因为这样随机数据都会有Hash冲突,根据生日悖论,随便找上sqrt(1e9)
个串就有大概率出现至少一对Hash 值相等的串(参见BZOJ 3098 Hash Killer II)。

最稳妥的办法是选择两个1e9 级别的质数,只有模这两个数都相等才判断相等,但常数略大,代码相对难写,目前暂时没有办法卡掉这种写法(除了卡时间让它超时)(参见BZOJ 3099 Hash Killer III)。

如果能背过或在考场上找出一个10^18级别的质数(Miller-Rabin),也相对靠谱,主要用于前一种担心会超时,后一种担心被卡。

偷懒的写法就是直接使用unsigned long long,不手动进行取模,它溢出时会自动对2^64进行取模,如果出题人比较良心,这种做法也不会被卡,但这个是完全可以卡的,卡的方法参见BZOJ 3097 Hash Killer I。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值