牛客网暑期ACM多校训练营(第三场)E 题解
题意就是一个字符串,复制前n-1个字符后问你有多少种长度n的子串,然后把它们的下标按种类输出来。
1:我们可以找到规律:
(1)对于长度为n的串如果他是一个长度为m的串经过n/m次循环复制而来的,即存在长度为m的循环节。
比如ababab是ab复制三次来的,那么我们这个串首尾相连成环后就存在m种长度为n/m的子串,这个画个图很容易证明。 (2)那么如果没有循环节呢,比如ababca,我们发现他首尾相连成环后一定有n种不同的长度为n的子串。因为成环后,每个长度为n的子串肯定包含一个c,并且每个子串中c的位置都是不同的。
如下:
ababca
babcaa
abcaab
bcaaba
caabab
aababc
那么一定存在n种长度为n的不同子串。
具体做法是利用kmp的next数组。我们知道对于一个长度为n的数组,如果n可以整除next[n]那么他就存在长度为n-next[n]的循环节。求出循环节剩下的就是简单的代码了。 2:我们队当时用后缀数组,具体就是把串前n-1个字符复制一遍,然后跑后缀数组,利用heigh数组的性质,我们可以知道heigt数组求的sa[i]和sa[i-1]的最长公共前缀,那么我们找heigh[i]>=n,然后把sa[i]和sa[i-1]归为一类就行了。
我们开始用nlogn的算法一直在T啊T啊T。神仙队友说nlogn,n才2e6怎么会超时,应该是排序部分超了,然后优化排序,继续T啊T,突然想到有个DC3优化,换DC3后炸内存QAQ,发现数组开多了。有些数组是不需要开三倍的。改一下就过了。
下面给出代码 #include <bits/stdc++.h>
using namespace std;
int nex[1000050];
char s[1000050];
void get_next()
{
nex[0]=0;
nex[1]=0;
int m=strlen(s);
for(int i=1; i<m; i++)
{
int j=nex[i];
while(j&&s[i]!=s[j]) j=nex[j];
nex[i+1]=s[i]==s[j]?j+1:0;
}
}
int main()
{
scanf("%s",s);
get_next();
int n=strlen(s);
//for(int i=1;i<=n;i++)
//printf("%d %d\n",i,nex[i]);
int x=n-nex[n];
if(n%x!=0)
{
printf("%d\n",n);
for(int i=0; i<n; i++)
printf("1 %d\n",i);
}
else
{
printf("%d\n",x);
for(int i=0; i<x; i++)
{
printf("%d",n/x);
for(int j=i;j<n;j+=x)
printf(" %d",j);
printf("\n");
}
}
return 0;
} #include<cstdio>
#include<algorithm>
#include<queue>
#include<iostream>
#include<cmath>
#include<cstring>
using namespace std;
#define F(x) ((x)/3+((x)%3==1?0:tb))
#define G(x) ((x)<tb?(x)*3+1:((x)-tb)*3+2)
const int MAXN = 2e6 + 5;//n*10
int sa[MAXN*3];
int Rank[MAXN];
int Height[MAXN];
int n;
char s[MAXN*3];
int r[MAXN*3];
int wa[MAXN*3],wb[MAXN*3],wv[MAXN*3];
int wws[MAXN*3];
void sort(int *r,int *a,int *b,int n,int m)
{
int i;
for(i=0;i<n;i++) wv[i]=r[a[i]];
for(i=0;i<m;i++) wws[i]=0;
for(i=0;i<n;i++) wws[wv[i]]++;
for(i=1;i<m;i++) wws[i]+=wws[i-1];
for(i=n-1;i>=0;i--) b[--wws[wv[i]]]=a[i];
return;
}
int c0(int *r,int a,int b)
{return r[a]==r[b]&&r[a+1]==r[b+1]&&r[a+2]==r[b+2];}
int c12(int k,int *r,int a,int b)
{if(k==2) return r[a]<r[b]||r[a]==r[b]&&c12(1,r,a+1,b+1);
else return r[a]<r[b]||r[a]==r[b]&&wv[a+1]<wv[b+1];}
void dc3(int *r,int *sa,int n,int m)
{
int i,j,*rn=r+n,*san=sa+n,ta=0,tb=(n+1)/3,tbc=0,p;
r[n]=r[n+1]=0;
for(i=0;i<n;i++) if(i%3!=0) wa[tbc++]=i;
sort(r+2,wa,wb,tbc,m);
sort(r+1,wb,wa,tbc,m);
sort(r,wa,wb,tbc,m);
for(p=1,rn[F(wb[0])]=0,i=1;i<tbc;i++)
rn[F(wb[i])]=c0(r,wb[i-1],wb[i])?p-1:p++;
if(p<tbc) dc3(rn,san,tbc,p);
else for(i=0;i<tbc;i++) san[rn[i]]=i;
for(i=0;i<tbc;i++) if(san[i]<tb) wb[ta++]=san[i]*3;
if(n%3==1) wb[ta++]=n-1;
sort(r,wb,wa,ta,m);
for(i=0;i<tbc;i++) wv[wb[i]=G(san[i])]=i;
for(i=0,j=0,p=0;i<ta && j<tbc;p++)
sa[p]=c12(wb[j]%3,r,wa[i],wb[j])?wa[i++]:wb[j++];
for(;i<ta;p++) sa[p]=wa[i++];
for(;j<tbc;p++) sa[p]=wb[j++];
return;
}
int K;
void calHeight(int *r, int *sa, int n)
{
int i, j, k = 0;
for (i = 1; i <= n; ++i) Rank[sa[i]] = i;
for (i = 0; i < n; Height[Rank[i++]] = k)
for (k ? k-- : 0, j = sa[Rank[i] - 1]; r[i + k] == r[j + k]; ++k);
return;
}
vector<int> q[MAXN];
bool vis[MAXN];
struct ac{
int index, id;
}b[MAXN];
int cmp1(ac d, ac f)
{
return d.id < f.id;
}
inline void solve()
{
int cnt = 0, f = 0;
for(int i = 1; i <= n; i++)
{
if(Height[i] >= K)
{
if(!f) //如果是一个新种类
{
f = 1;
cnt++;
q[cnt].push_back(sa[i - 1]);
vis[sa[i - 1]] = 1;
}
q[cnt].push_back(sa[i]);
vis[sa[i]] = 1;
}
else f = 0;
}
for(int i = 0; i < K; i++) //统计只一次的
{
if(!vis[i])
q[++cnt].push_back(i);
}
for(int i = 1; i <= cnt; i++) sort(q[i].begin(), q[i].end());
for(int i = 1; i <= cnt; i++)
{
b[i].id = q[i][0];
b[i].index = i;
}
sort(b + 1, b + cnt + 1, cmp1);
printf("%d\n", cnt);
for(int i = 1; i <= cnt; i++)
{
int index = b[i].index;
int len = q[index].size();
printf("%d ", len);
for(int j = 0; j < len; j++)
printf("%d%c", q[index][j], j == len - 1 ? '\n' : ' ');
}
}
int main()
{
scanf("%s",s);
int Max=-1;
n=strlen(s);
for(int i = 0; i < n - 1; i++) s[i + n] = s[i];
K = n;
n += n - 1;
for(int i=0;i<n;i++){
r[i]=s[i];
if(r[i]>Max)Max=r[i];
}
r[n]=0;
dc3(r,sa,n+1,Max+1);
calHeight(r,sa,n);
solve();
return 0;
}
查看17道真题和解析