题目大意:
给你n个串,求在至少n/2(大于n/2)个串出现的最长子串,按字典序从小到大输出。
解题思路:
日常二分,二分长度,然后对其存在进行判断即可。
这里我感觉用set处理起来比较简单,对于height[i]大于等于k的值,直接把 pos[sa[i]] 和 pos[sa[i-1]] 加入到set中。最后判断set中的元素个数是否大于n/2,如果大于 就把 sa[i] 储存即可。。。
刚开始犯了好几个智障的问题,
第一,第一次判断每个位置属于哪个字符串 即 pos数组 我是用一个数组记录每个串的结束位置然后二分的,其实直接在构造串的过程中把pos数组构造了即可。
第二,我当时判断set中元素个数合法以后是遍历height数组 把所有 那一类的 sa[i] 加进去的,其实明显不用,因为同一类他们表示的是同一个子串,是要被去重去掉的= = 。所以只加一个 sa[i]即可。
第三,为了字典序最小,开始写了个string 排了序,还顺带去重了一下,也是明显不需要,因为 sa数组本来就是按照字典序排的,你按照sa数组跑出来的自然是最小字典序。。。
除去这些还算是一道比较正常的题目吧。
Ac代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <vector>
#include <queue>
#include <stack>
#include <map>
#include <algorithm>
#include <set>
#include <functional>
#define next ne
#define rank ra
using namespace std;
typedef long long ll;
const int maxn=2e5+5;
const int INF=1e9+7;
char s[maxn];
int V[maxn],b[maxn];
vector<int> ans;
set<int> st;
int n,m,cnt,sa[maxn],rank[maxn],height[maxn];
int t1[maxn],t2[maxn],r[maxn],c[maxn];
bool cmp(int *r,int a,int b,int l)
{
return r[a]==r[b] && r[a+l]==r[b+l];
}
void da(int str[],int sa[],int rank[],int height[],int n,int m)
{
n++;
int i,j,p,*x=t1,*y=t2;
for(int i=0;i<m;i++) c[i]=0;
for(int i=0;i<n;i++) c[x[i]=str[i]]++;
for(int i=1;i<m;i++) c[i]+=c[i-1];
for(int i=n-1;i>=0;i--) sa[--c[x[i]]]=i;
for(int j=1;j<=n;j<<=1)
{
p=0;
for(int i=n-j;i<n;i++) y[p++]=i;
for(int i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j;
for(int i=0;i<m;i++) c[i]=0;
for(int i=0;i<n;i++) c[x[y[i]]]++;
for(int i=1;i<m;i++) c[i]+=c[i-1];
for(int i=n-1;i>=0;i--) sa[--c[x[y[i]]]]=y[i];
swap(x,y);
p=1,x[sa[0]]=0;
for(int i=1;i<n;i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
if(p>=n) break;
m=p;
}
int k=0;
n--;
for(int i=0;i<=n;i++) rank[sa[i]]=i;
for(int i=0;i<n;i++)
{
if(k) k--;
j=sa[rank[i]-1];
while(str[i+k]==str[j+k]) k++;
height[rank[i]]=k;
}
}
bool check(int lt,int flag)
{
st.clear();ans.clear(); //注意这里set要清空
for(int i=2;i<=n;i++)
{
if(height[i]>=lt)
{
st.insert(b[sa[i-1]]); //直接加入set即可
st.insert(b[sa[i]]); //满足条件就return 1
if(st.size()>m&&flag==0) return 1; //flag是为了方便之后的运算
}
else //如果flag为1 就将sa[i]加入答案
{
if(st.size()>m&&flag) ans.push_back(sa[i-1]);
st.clear();
}
}
if(st.size()>m&&flag) ans.push_back(sa[n]); //最后记得也要判断一下
return 0;
}
int main()
{
while(scanf("%d",&m)!=EOF)
{
if(m==0) break;
st.clear();
int len=0,num=30;cnt=0;
for(int i=1;i<=m;i++) //处理出 r 数组
{ //同时处理出 pos 数组 即 b 数组
scanf(" %s",s);
int ls=strlen(s);
for(int j=0;j<ls;j++) b[len]=i,r[len++]=s[j]-'a'+1;
r[len++]=num++;
}
m=m/2; //除2 方便之后的运算
r[len-1]=0;n=len;
da(r,sa,rank,height,n,num);
int L=0,R=INF,res=0;
while(L<=R) //二分长度
{
int mid=(L+R)>>1;
if(check(mid,0)) res=mid,L=mid+1;
else R=mid-1;
}
if(res==0) printf("?\n");
else
{
check(res,1); //这里重新跑一遍 check
for(int i=0;i<ans.size();i++) //输出所有的子串
{
for(int j=ans[i];j<ans[i]+res;j++)
printf("%c",r[j]-1+'a');
printf("\n");
}
}
printf("\n");
}
//system("pause");
}