Fancy Signal Translate 【二分 + 滚动哈希】
链接:https://ac.nowcoder.com/acm/problem/14270
来源:牛客网
题目描述
FST是一名可怜的小朋友,他很强,但是经常fst,所以rating一直低迷。
但是重点在于,他真的很强!他发明了一种奇特的加密方式,这种加密方式只有OIer才能破解。
这种加密方式是这样的:对于一个01串,他会构造另一个01串,使得原串是在新串中没有出现过的最短的串。
现在FST已经加密好了一个串,但是他的加密方式有些BUG,导致没出现过的最短的串不止一个,他感觉非常懊恼,所以他希望计算出没出现过的最短的串的长度。
输入描述:
一行,一个01串。长度≤105
输出描述:
一行,一个正整数,表示没有出现过的最短串的长度。
示例1
输入
100010110011101
输出
4
不清楚题意的我,闷着头就想敲 二分+哈希,都不知道题意让干啥,一直w,后来看了别人代码,才了解到了题意啊啊啊!!!
题意:
给一个 01 组成的字符串,问 最短的 不和字符串的任意子串重复的 串的长度是多少。
例子:
就比如:对于给出的串 11101,那么长度为2的串 00 就没有和11101的任意子串重复。而 1 和 0 这种长度为1的串就在11101 的子串出现过。
思路:
因为我实在太想复习二分和滚动哈希了,就进坑了
- 对于答案的情况我们用二分列举出来 ,
- 对于判断 mid 是否是答案 ,我们用滚动哈希,得到所有的长度为 mid 的子串的哈希值的个数,用map标记。
- 对于所有不重复的标记的数量若小于 1<< mid ,则说明还有某些情况的子串没有出现,则 mid 为可行解。
问题:为什么二进制左移可以判断是否是可行解?
看一个例子,mid = 3, 1<<3 = 8 , 得出来的 8 也就是三位 01 的串的8个种类 :000,001,010,011,100,101,110,111
所以如果遍历出来长度等于 mid 的 01 串的种类数小于 1<<mid 个的话,说明还有一些没有出现过的01串,则mid为可行解,我们用二分找出最小的mid即可。
撸代码:
/*二分 + 滚动哈希*/
#include<string.h>
#include<stdio.h>
#include<map>
using namespace std;
typedef unsigned long long ull;
ull B = 233;//基数
int n;
char s[100010];
int work(int len){
ull val=0,p=1;
for(int i=0;i<len;i++){
val=val*B+s[i]-'0';
p*=B;
}//先得出 0~len-1 的哈希值 val
ull temp=val;
map<ull,bool>book;
book.clear();
book[temp]=1;// 标记所出现过的串的哈希值
int cnt=1;
for(int i=0;i+len<n;i++){
temp=temp*B+(s[i+len]-'0')-(s[i]-'0')*p;/*滚动哈希*/
if(!book[temp]){
cnt++;//记录出现过几种
book[temp]++;
}
}
return cnt==(1ll<<len);// 判断长为len的 01 串种类是否完全出现过 1LL 转型long long
}
int main()
{
scanf("%s",s);
int r=18,l=1,mid;
n=strlen(s);
/*二分求解*/
int ans;
while(l<=r){
mid=(l+r)/2;
/*如果长度为mid出现重复串*/
if(work(mid)){
l=mid+1;
}else {
r=mid-1;
ans=mid;
}
}
printf("%d\n",ans);
return 0;
}
第二种思路:
使用 set 容器直接存储 string 的 长度为 i 的子串
#include<set>
#include<iostream>
using namespace std;
int main()
{
string s;
cin>>s;
for(int i=1;;i++){
set<string>book;/*定义set容器 把字符串直接不重复插入*/
for(int j=0;j+i<=s.size();j++){
book.insert(s.substr(j,i));/*从 j 开始 长度为i的子串*/
}
if(book.size()<(1<<i)){/*若是出现不重复*/
cout<<i<<endl;
break;
}
}
return 0;
}