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