#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e6+9;//一般有2n个等价类
int tot=1,las=1;
char s[maxn];
long long ans=0;
struct NODE
{
int ch[26];
int len,fa;
NODE()
{
memset(ch,0,sizeof(ch));
len=fa=0;
}
} dian[maxn];
void add(int c)//构造自动机
{
int p=las,np=las=++tot;
dian[np].len=dian[p].len+1;
for(; p&&!dian[p].ch[c]; p=dian[p].fa)
dian[p].ch[c]=np;
if(!p)dian[np].fa=1;
else
{
int q=dian[p].ch[c];
if(dian[q].len==dian[p].len+1)
dian[np].fa=q;
else
{
int nq=++tot;
dian[nq]=dian[q];
dian[nq].len=dian[p].len+1;
dian[q].fa=dian[np].fa=nq;
for(; p&&dian[p].ch[c]==q; p=dian[p].fa)
dian[p].ch[c]=nq;
}
}
}
long long coun_dif_num()//找本质不同的子串的数量
{
long long ans = 0;
for(int i= 1; i<=tot; i++)
{
ans +=dian[i].len-(dian[dian[i].fa].len+1)+1;
//ans += max(i)-min(i)+1;每个等价类中的字符串数量
}
printf("%lld\n",ans);
return ans;
}
void all_len_max_appear()//求每个长度的子串最多的出现次数
{
for(int i = 1; i<=tot; i++)
{
f[dian[i].len] = max(f[dian[i].len],r[i]);
}
for(int i = len; i>=1; i--)
{
f[i] = max(f[i],f[i+1]);
}
for(int i = 1; i<=len; i++)
{
printf("%d\n",f[i]);
}
//更新时应更新F[j]=max(F[j],|Right[i]|)j∈[minlen[i],len[i]],但其实不必每个都更新,
//因为如果长度为n的字符串出现了m次,那么长度为n-1的字符串一定至少出现了m次,及F[i]>=F[j](i<j),
//所以对于每个点只需要更新F[len[i]]=max(F[i],|Right[i]|),最后从大到小更新一下F就好了。
}
void find_min_s(int n)//求串的最小表示,原串创建自动机时要加长一倍
{
int now,i,j;
now = 1;
char ans[maxn];
for(i = 0; i <n; i++)
{
for(j = 0; j<26; j++)
{
if(dian[now].ch[j]!=0)
{
now = dian[now].ch[j];
ans[i] = j+'a';
break;
}
}
}
ans[n] = 0;
}
void Right()//求每个状态的right集合大小
{
for(int i = 0,p = 1; i<len; i++) //主链上right+1
{
p = dian[p].ch[s[i]-'a'];
r[p]++;
}
for(int i = 1; i<=tot; i++)//基数排序
{
b[dian[i].len]++;
}
for(int i = 1; i<=len; i++)
{
b[i]+=b[i-1];
}
for(int i = 1; i<=tot; i++)
{
id[b[dian[i].len]--] = i;
}
for(int i = tot; i>=1; i--)//parent树自地向上更新right集合大小
{
r[dian[id[i]].fa]+=r[id[i]];
}
}
int max_commom(char s2[])//计算s和自动机字符串的最长公共子串
{
int ans = 0,len = 0;
int now = 1,len_s = strlen(s);
for(int i = 0; i<len_s; i++)
{
while(now&&dian[now].ch[s[i]-'a']==0)
{
now = dian[now].fa;
len = dian[now].len;
}
if(now)
{
len++;
now = dian[now].ch[s[i]-'a'];
}
else
{
now = 1;
len = 0;
}
ans = max(ans,len);
}
return ans;
}
void mul_max_commom(char s2[])//多个串的最长公共子串
{
int ans = 0,len = 0;
int now = 1,len_s = strlen(s2);
for(int i = 0; i<len_s; i++)
{
while(now&&dian[now].ch[s[i]-'a']==0)
{
now = dian[now].fa;
len = dian[now].len;
}
if(now)
{
len++;
now = dian[now].ch[s[i]-'a'];
sing_len[now] = max(sing_len[now],len);
}
else
{
now = 1;
len = 0;
}
}
for(int u = tot; u>=1; u--)
{
int i = id[u];
if(dian[i].fa)
{
sing_len[dian[i].fa] = max(sing_len[dian[i].fa],min(sing_len[i],dian[dian[i].fa].len));
//长度较长的子串可以匹配,那么长度较短的子串也可以匹配
}
all_len[i] = min(sing_len[i],all_len[i]);
sing_len[i] = 0;
}
//return max(all_len[i]);
}
int deep(int x)//求deep,为了求第k字典序
{
if(dep[x]>0)
return dep[x];
int ans = 0;
for(int i = 0;i < 26;i++)
{
if(dian[x].ch[i])
{
ans +=deep(dian[x].ch[i]);
}
}
dep[x] = ans+1;
return dep[x];
}
void find_min(int num)//求字典序第k小子串
{
int now = 1,tail = -1;
while(num>0)
{
int sum = 0;
for(int i = 0;i < 26;i++)
{
if(!dian[now].ch[i])
continue;
if(sum+dep[dian[now].ch[i]]>=num)
{
now = dian[now].ch[i];
s[++tail] = i+'a';
num --;
break;
}
else
{
sum+=dep[dian[now].ch[i]];
}
}
num -= sum;
}
s[++tail] = 0;
}
int main()
{
int cd;
scanf("%s",s);
cd=strlen(s);
for(int i=0; i<cd; i++)
{
add(s[i]-'a');
}
coun_dif_num();
for(int i=0; i<cd; i++)
{
add(s[i]-'a');
}
find_min_s(cd);
return 0;
}