https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&category=28&problem=2514&mosmsg=Submission+received+with+ID+1809543
题意:给一个m
给一个字符串 (最大长度40000)
找出字符串中重复出现m次以上(包括m) 的最长子串
例如 babab 出现bab两次
如果不存在 输出none ,否则输出最长的长度和 该长度最靠右的起始位置
思路:
可以二分答案L, 范围是【1,n】
每次判断当前的L是否合法,即给出的字符串中,是否存在 长度为L的字符串出现了m次以上
我们直接暴力计算整个字符串中 每一段长度为L的子串,统计hash值是否有出现m次以上的,不断缩小 L的范围
复杂度:
main中的 二分是logn ,判断函数ok()的复杂度是 O(n)+nlogn+o(n)、总复杂度logn*n*logn //1.7S压线过、、、
//ps:之前 的判断函数是用 map写...不知道是不是因为最极端情况是 O(n)+nlogn+nlogn。常数大一点。。TLE了一万年。。。(与上面相比多了一个 ) n*logn*logn
//PS1: 后来发现, hash[i] = hash[i-1]*p + s[i] ; 这些计算都可以不用取模,因为即使溢出了,相同字符串得到的hash值必然也是一样的....
代码1的 总复杂度logn*O(n)*logn ...感觉这个方法要优化主要应该是把 括号里面的那个nlogn---->O(n)
优化的思路其实还是hash , 直接把每个字符串的hash值 与 次数对应起来。就可能在ON内解决了...(用map就nlogn了...)
对字符串的 长度为X的(n-x)段 子串 ,我们可以得到n-x个hash(近似看作n个吧..).
然后怎么把n个数在O(n)内得到 重复的hash值个数....最早想的就是直接丢到map里然后在遍历一遍...(nlogn+nlogn)、或者是直接排序再遍历(nlogn+o(n))
最后的解决方案是: 把n个数映射到一个hash数组, hashnum取1<<16;(略大于题目的max_m=40000)
unsigned long long mod_hash[hashnum];//hash本质就是映射,本处把字符串的hash值x 映射到 x%hashnum这个位置
//并且为了避免冲突(由于内存限制hashnum取得有点小),在mod_hash 存该x%hashnum的原始值x;
//如果下次遇到一个Y%hashnum==x%hashnum,我们要判断是否x==y,如果是,则对应的num[x%hashnum]++;
//如果不等,那我们把y映射到下一个位置,即x%hashnum+1(如果还冲突继续往后映射)..因为hash值本身冲突概率就小,所以这个的操作
//应该大多数情况是O(1)、个别情况也是较小的常数
unsigned long longnum[hashnum];//存hash值为x的子串的数量
总复杂度logn*o(n) 最终run time 0.309S;
代码1 : n*logn*logn
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <iostream>
#include <queue>
#include <map>
#include <set>
#include <vector>
using namespace std;
long long n,m,k;
const long long N = 40005 ;
char tm[N];
long long min(long long a,long long b)
{return a<b?a:b;}
long long max(long long a,long long b)
{return a>b?a:b;}
long long prime[N],hash[N];
const long long mod =1000000007;
const long long fact =239;
long long last_right_start=-1;
int main()
{
long long ok(long long );
prime[0]=1;
long long i;
for (i=1;i<=N;i++)
{
prime[i]=(prime[i-1]*fact)%mod;
}
long long l,r;
while( scanf("%lld", &m )!=EOF)
{
if (!m) break;
getchar();
scanf("%s",tm);
n=strlen(tm);
l=1;
r=n+1;
if (!ok(1))
{
printf("none\n");
continue;
}
while(r-l>1)
{
long long mid=(l+r)/2;
if (ok(mid))
l=mid;
else
r=mid;
}
ok(l); //得到last_right_start;
printf("%lld %lld\n",l,last_right_start);
}
return 0;
}
struct node
{
int x;
int pos;
node(){}
node(int a,int c)
{
x=a;pos=c;
}
};
node tnd[N]; //记录某个hash值以及对应左起点;
int cmp(node a,node b) //按hash排序,同hash值按左端点坐标排序
{
if (a.x!=b.x)
return a.x<b.x;
else
return a.pos<b.pos;
}
long long ok(long long x)
{
last_right_start=-1;
long long i;
long long hash=0;
long long cun=0;
for (i=n-x;i<n;i++) //计算最后一段长度为x的hash
{
hash=(hash+tm[i]*prime[cun++])%mod;
}
cun=1;
tnd[cun]=node(hash,n-x);
cun++;
for (i=n-x-1;i>=0;i--) //递推剩下的长度为x的hash值
{
hash=(hash+mod-(tm[i+x]*prime[x-1])%mod)%mod;
hash=(hash*fact)%mod;
hash=(hash+tm[i]*prime[0])%mod;
tnd[cun]=node(hash,i);
cun++;
}
sort(tnd+1,tnd+cun,cmp);
long long flag=0;
int tmp=0;
for (i=1;i<cun;i++) //把hash值相同的累计,最后存下个数>=m的最远左坐标
{
if (i==1||tnd[i].x!=tnd[i-1].x)
tmp=0;
tmp++;
if (tmp>=m )
{
flag=1;
last_right_start=max(last_right_start,tnd[i].pos);
}
}
if (flag) return 1;
else
return 0;
}
代码2: nlogn;
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <iostream>
#include <queue>
#include <map>
#include <set>
#include <vector>
typedef unsigned long long uLL ;
using namespace std;
int n,m,k;
const int N = 40050 ;
char tm[N];
uLL min(uLL a,uLL b)
{
return a<b?a:b;
}
uLL max(uLL a,uLL b)
{
return a>b?a:b;
}
uLL prime[N],HASH[N];
//const uLL mod =1000000007;
const uLL fact =239;
int last_right_start;
int main()
{
bool ok(int l );
prime[0]=1;
int i;
for (i=1; i<N; i++)
{
prime[i]=(prime[i-1]*fact);
}
int l,r;
while( scanf("%d", &m )!=EOF)
{
if (!m)
break;
getchar();
scanf("%s",tm+1);
n=strlen(tm+1);
HASH[0]=0;
for(int i=1; i<=n; i++) //利用预处理结果算出该段字符串hash值
HASH[i] = HASH[i-1]*fact + tm[i] ;
l=1;
r=n;
if (!ok(1))
{
printf("none\n");
continue;
}
while(l<=r)
{
int mid=(l+r)/2;
if (r-l==0) //只有一个待选答案
break;
if (r-l==1)//2选1
{
if (!ok(r)) r=l;
break;
}
if (ok(mid))//不断逼近,确保每次[L,R]内都是可能的合法答案
l=mid; //mid可能合法
else
r=mid-1; //mid一定不合法
}
ok(r); //取r对应的last_right_start
printf("%d %d\n",r,last_right_start-1);
}
return 0;
}
const int hashnum=1<<16;
uLL mod_hash[hashnum]; //hash本质就是映射,本处把字符串的hash值x 映射到 x%hashnum这个位置
//并且为了避免冲突(由于内存限制hashnum取得有点小),在mod_hash存的原始值x;
//如果下次遇到一个Y%hashnum==x%hashnum,我们要判断是否x==y,如果是,则对应的num[x%hashnum]++;
//如果不等,那我们把y映射到下一个位置,即x%hashnum+1(如果还冲突继续往后映射)..因为hash值本身冲突概率就小,所以这个的操作可以近似看作O(1)[未经证明];
uLL num[hashnum]; //存hash值为x的子串的数量
uLL push_in_hash (uLL x)
{
int tmp=x%hashnum;
while(mod_hash[tmp]!=x &&num[tmp])//如果mod_hash[tmp]存的原始值与x不相等,只能把x映射到mod_hash[tmp+1]
tmp++;
mod_hash[tmp]=x; //把tmp和x映射起来
num[tmp]++;
return num[tmp];
}
bool ok(int l )
{
memset(mod_hash,0,sizeof(mod_hash));
memset(num,0,sizeof(num));
bool ok = false ;
int cun=1;
uLL sb=last_right_start=0;
for( int i=1; i+l-1<=n; i++)
{
uLL tmp = HASH[i+l-1] - HASH[i-1] * prime[l];
sb=push_in_hash(tmp); //得到当前字符串出现过的次数
if(sb>=m)
{
last_right_start=i; //更新start位置
ok=true;
}
}
return ok ;
}
后来再次写的代码。。。
wa半天。。。发现是hash的数组越界了。。。不知道为什么上面的代码不会越界。。。。
has数组以后要开比mod大500左右,mod要尽可能选素数,不要随便选1<<16
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#include <vector>
#include <iostream>
#define ull unsigned long long
using namespace std;
char tm[40555];
int len,m;
ull pp=239;
const ull mod= 1<<16 ;
ull has[mod+50];
ull num[mod+50];
int rightest=0;
ull pre_hash[40555];
ull prime[40555];
ull get_hash(int st,int x)
{
if (st==0) return pre_hash[st+x-1];
return pre_hash[st+x-1]-pre_hash[st-1]*prime[x];
}
ull push(ull ret)
{
int dd=ret%mod;
while(has[dd]!=ret&&num[dd])
dd++;
num[dd]++;
has[dd]=ret;
return num[dd];
}
int bin(int x)
{
memset(has,0,sizeof(has));
memset(num,0,sizeof(num));
rightest=0; //要在此处初始化
int flag=0;
for (int i=0;i+x-1<len;i++)
{
ull ret=get_hash(i,x);
int re=push(ret);
if ( re>=m) // 之前一直写re>maxx 煞笔了
{
// maxx= re;
rightest=i; flag=1;
}
}
return flag;
}
int main()
{
int i,j;
prime[0]=1;
for (i=1;i<=40000;i++)
prime[i]=prime[i-1]*pp;
while(cin>>m&&m)
{
scanf("%s",tm);
len= strlen(tm);
pre_hash[0]=tm[0];
for(i=1;i<len;i++)
pre_hash[i]=pre_hash[i-1]*pp+tm[i];
int l=0;
int r=len;
int ans=-1;
while(l<=r)
{
if (r-l<=1)
{
if (bin(r))
ans=r;
else
ans=l;
break;
}
int mid=(l+r)>>1;
if (bin(mid))
l=mid;
else
r=mid-1;
}
bin(ans);
if (ans==0)
printf("none\n");
else
printf("%d %d\n",ans,rightest);
}
return 0;
}