LA-4513 - Stammering Aliens-(hash字符串+二分答案+hash排序) 找出子串出现次数

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;
}





你可能感兴趣的:(LA-4513 - Stammering Aliens-(hash字符串+二分答案+hash排序) 找出子串出现次数)