给一个字符串 w w w,求它的临界指数。
设 w w w 的子串 t t t ,它由它的一个前缀循环 α \alpha α 次构成, α \alpha α 可以是分数。
临界指数就是最大的 α \alpha α.
本来想按这道题的做法,枚举基值长度 r r r,在 r r r的所有倍数上枚举起始位置 s t st st,然后尽量朝左朝右扩展。
但这是错的,无法处理分数倍数的长度恰好卡在两个整数倍数之间的情况
比如abcdefghd
,答案应该是
6
/
5
6/5
6/5,但按上述方法只能求出
1
/
1
1/1
1/1.
正确做法
正确的做法是先做后缀数组,求出sa和height.
对于任意 a < b a<b a<b,它们可以贡献一个答案 L C P + S U B S U B \frac {LCP+SUB}{SUB} SUBLCP+SUB,其中
L C P = m i n i = a + 1 b { h e i g h t [ i ] } LCP=min_{i=a+1}^{b}\{height[i]\} LCP=mini=a+1b{height[i]}, S U B = ∣ s a [ a ] − s a [ b ] ∣ SUB=|sa[a]-sa[b]| SUB=∣sa[a]−sa[b]∣.
看到分数形式的最优化问题,自然想到分数规划…个屁啊。
我们可以枚举从大到小 L C P LCP LCP,然后求最小的 S U B SUB SUB.
如果把height数组看成直方图上的高度,从上到下扫描,横向会依次扩展。
每扩展一个方格,或者将两个矩形连通,实际上相当于做了集合合并。
我们用并查集来维护集合合并的过程,集合中存储的数字是 s a [ i ] sa[i] sa[i]。
以上部分的思路和[NOI2015]品酒大会基本相同,但是并查集中需要查询的东西不同。
维护最小的差
对于每次集合合并,需要知道合并后集合中最近的两个数的差。
为此,可以用 s e t set set维护集合,启发式合并(把小的并入大的)来更新差。
合并之后要记得把小的集合清空。
复杂度分析
后缀数组 n l o g n nlogn nlogn,并查集认为是 O ( n ) O(n) O(n), s e t set set多一个 l o g log log,启发式合并多一个 l o g log log
总复杂度 n l o g 2 . nlog^2. nlog2.
总结:
- 一个愚蠢的wa点:合并集合时忘了插入。
- 后缀数组处理和lcp有关的问题非常实用,很多时候可以枚举height.
- 如果做过类似的题当然很好,但是一旦链接到一个错误的题上,就会浪费大量的时间与提交。
- nwrrc拿金太难了qwq
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int M = 200016, MOD = 1000000007;
inline int read()
{
int x=0, f=1; char ch = getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0'; ch=getchar();}
return x*f;
}
/*
后缀从0开始,排名从1开始
sa[i]表示排名第i的后缀是谁,下标是[1,n],值是[0,n-1]
ra[i]表示第i个后缀的排名,下标是[0,n-1],值是[1,n]
height[i]表示排名第i的后缀与排名第i-1的后缀的LCP,下标是[2,n]
*/
namespace SA
{
void st_init(int *arr, int n);
/* 后缀数组 */
int sa[M], ra[M], height[M]; //后缀三数组,sa和ra下标从0开始,height下标从1开始
int t1[M], t2[M], c[M]; // 用于基数排序的三个辅助数组
void build(char *str, int n, int m) // 构造后缀三数组,字符串下标从0开始,n表示长度,m表示字符集大小
{
str[n] = 0;
n++;
int i, j, p, *x = t1, *y = t2;
for(i = 0; i < m; i++) c[i] = 0;
for(i = 0; i < n; i++) c[x[i]=str[i]]++;
for(i = 1; i < m; i++) c[i] += c[i-1];
for(i = n-1; i >= 0; i--) sa[--c[x[i]]] = i;
for(j = 1; j <= n; j<<=1)
{
p = 0;
for(i = n-j; i < n; i++) y[p++] = i;
for(i = 0; i < n; i++) if(sa[i] >= j) y[p++] = sa[i]-j;
for(i = 0; i < m; i++) c[i] = 0;
for(i = 0; i < n; i++) c[x[y[i]]]++;
for(i = 1; i < m; i++) c[i] += c[i-1];
for(i = n-1; i >= 0; i--) sa[--c[x[y[i]]]] = y[i];
swap(x, y);
p = 1; x[sa[0]] = 0;
for(i = 1; i < n; i++)
x[sa[i]] = (y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+j]==y[sa[i]+j]) ? p-1 : p++;
if(p >= n) break;
m = p;
}
n--;
for(int i = 0; i <= n; i++) ra[sa[i]] = i;
for(int i=0, j=0, k=0; i < n; i++)
{
if(k) k--;
j = sa[ra[i]-1];
while(str[i+k]==str[j+k]) k++;
height[ra[i]] = k;
}
st_init(height, n);
}
/* ST表 */
int lg[M];
int table[20][M];
void st_init(int *arr, int n)
{
if(!lg[0])
{
lg[0]=-1;
for(int i=1;i<M;i++)
lg[i]=lg[i/2]+1;
}
for(int i=1; i<=n; ++i)
table[0][i] = arr[i];
for(int i=1; i<=lg[n]; ++i)
for(int j=1; j<=n; ++j)
if(j+(1<<i)-1 <= n)
table[i][j] = min(table[i-1][j], table[i-1][j+(1<<(i-1))]);
}
// 查询第l个后缀和第r个后缀的LCP,下标从0开始
int lcp(int l, int r)
{
l = ra[l], r = ra[r];
if(l>r) swap(l,r);
++l;
int t = lg[r-l+1];
return min(table[t][l], table[t][r-(1<<t)+1]);
}
}
namespace UFS //迫真并查集
{
int fa[M];
int ans[M];
set<int> st[M];
void init(int n, int *sa)
{
for(int i=1; i<=n; ++i)
{
fa[i] = i;
st[i].insert(sa[i]);
ans[i] = n;
}
}
int find(int n)
{
return fa[n]==n ? fa[n] : fa[n] = find(fa[n]);
}
int join(int a, int b)
{
a = find(a), b = find(b);
if(st[a].size()>st[b].size()) swap(a,b);
fa[a] = b;
ans[b] = min(ans[a], ans[b]);
for(auto x:st[a])
{
auto it = st[b].lower_bound(x);
if(it!=st[b].end()) ans[b]=min(ans[b], *it-x);
if(it!=st[b].begin()) ans[b]=min(ans[b], x-*(--it));
st[b].insert(x);
}
st[a].clear();
return ans[b];
}
}
using P = pair<int,int>;
char str[M];
int main(void)
{
#ifdef _LITTLEFALL_
freopen("in.txt","r",stdin);
#endif
scanf("%s", str);
int n = strlen(str);
SA::build(str, n, 128);
using SA::height; using SA::sa;
UFS::init(n, sa);
vector<P> vc(n-1);
for(int i=2; i<=n; ++i)
vc.push_back({height[i], i});
sort(vc.begin(), vc.end(), greater<P>());
P ans= {1,1};
for(auto p:vc)
{
int sub = UFS::join(p.second,p.second-1);
P tmp = {p.first + sub, sub};
if(1ll*ans.first*tmp.second<1ll*tmp.first*ans.second)
ans = tmp;
}
int gcd = __gcd(ans.first, ans.second);
ans.first/=gcd, ans.second/=gcd;
printf("%d/%d\n",ans.first,ans.second );
return 0;
}