题解
我坚持使用考场上想出来的 O ( 26 n + n ln n ) O(26n+n\ln n) O(26n+nlnn) 的做法,并成功地切掉了这道题。
无他,但卡常耳。
我的做法是枚举 ( A B ) i (AB)^i (AB)i 的右边界 k k k。设 l i l_i li 表示子串 S 1.. i S_{1..i} S1..i 的最短循环,那么 A B AB AB 必定是由若干个 l k l_k lk 首尾相接组合而成的。我们可以直接枚举每一种可能的 A B AB AB ,时间复杂度 O ( n ln n ) O(n\ln n) O(nlnn) 。
那么每一个 A B AB AB 内有多少个合法的 A A A 使得 F ( A ) ≤ F ( C ) F(A)\le F(C) F(A)≤F(C) 呢?设 f i , j f_{i,j} fi,j 表示 S 1.. i S_{1..i} S1..i 中,满足 F ( S 1.. x ) ≤ j , x < i F(S_{1..x})\le j,x<i F(S1..x)≤j,x<i 的 x x x 的个数(也就是若以 S 1.. i S_{1..i} S1..i 为 A B AB AB ,其中合法的 A A A 的个数)。
求 f i , j f_{i,j} fi,j 的时间复杂度是 O ( 26 n ) O(26n) O(26n) 的,用它来计算答案则是 O ( 1 ) O(1) O(1) 的。
但是这样子做要枚举一个数的所有因子,由于这个上限很大,只能用 vector
了,这使得常数很大只有
84
84
84 分。
卡常及优化
下面开始卡常+优化。
优化1
用 reserve
函数可以提高 vector
的效率。这个我在考场上就用了,但是还是只有
84
84
84 分。
优化2
对于每个数
i
i
i ,只储存小于等于
i
\sqrt{i}
i 的因子。然后把因子数大于等于
38
38
38 的扔进 vector
,小于等于
38
38
38 的用数组直接存。再加上一系列奇奇怪怪的运算优化,终于在本地跑进了1s。然而交到OJ上还是 TLE84 了。
代码:
#pragma GCC optimize("fast-math","unroll-loops","no-stack-protector")
#pragma GCC diagnostic error "-funsafe-loop-optimizations"
#pragma GCC diagnostic error "-fcse-skip-blocks"
#pragma GCC diagnostic error "-fwhole-program"
#pragma GCC diagnostic error "-std=c++14"
#pragma GCC target("sse3","sse2","sse")
#pragma GCC optimize("Ofast")
#include<cstring>
#include<vector>
#include<cstdio>
#include<cmath>
using namespace std;
typedef long long ll;
#define fo(i,l,r) for(i=l;i<=r;++i)
#define N 1048585
const int P=998244353;
int a[N],len[N],num[N],lsl[N],id[N],fa[N][39],siz[N],b[N];
char s[5][N],buf[100005],*l=buf,*r=buf;
ll f[N][26],pow1[N];
vector<int>fac[10005];
bool cnt[26];
inline char gc()
{return l==r&&(r=(l=buf)+fread(buf,1,100005,stdin),l==r)?EOF:*l++;}
inline void read1(int &k)
{
char ch;while(ch=gc(),ch<'0'||ch>'9');k=ch-48;
while(ch=gc(),ch>='0'&&ch<='9') k=k*10+ch-48;
}
inline int read2(int k)
{
char ch;int len=0;
while(ch=gc(),ch<'a'||ch>'z');s[k][++len]=ch;
while(ch=gc(),ch>='a'&&ch<='z') s[k][++len]=ch;
return len;
}
inline int minus(int x,ll y){x-=y;return x<0?x+P:x;}
int main()
{
freopen("string.in","r",stdin);
freopen("string.out","w",stdout);
int T,t,n,tot,now1,lim,maxn=0,maxm,all=0;
ll ans;register int i,j,k;
read1(t),--t;
fo(i,0,t)
{
lsl[i]=read2(i);
if(lsl[i]>maxn) maxn=lsl[i];
}
maxm=sqrt(maxn),pow1[0]=1;
fo(i,1,maxn) pow1[i]=26*pow1[i-1]%P;
fo(i,1,maxm)
for(int j=i*i;j<=maxn;j+=i) ++num[j];
fo(i,1,maxn) if(num[i]>38)
id[i]=++all,fac[all].reserve(num[i]);
fo(i,1,maxm)
for(int j=i*i;j<=maxn;j+=i)
id[j]?fac[id[j]].push_back(i),0:fa[j][++siz[j]]=i;
fo(T,0,t)
{
ans=0,n=lsl[T],tot=0,
memset(b,0,sizeof b),
memset(len,0,sizeof len),
memset(cnt,0,sizeof cnt);
fo(i,1,n) a[i]=(26LL*a[i-1]+s[T][i]-97)%P;
fo(i,1,n)
{
(cnt[s[T][i]-97]^=1)?++tot:--tot;
fo(j,0,25) f[i][j]=f[i-1][j];
++f[i][tot],b[i]=tot;
}
fo(i,1,n) fo(j,1,25) f[i][j]+=f[i][j-1];
fo(i,1,n) if(!len[i])
{
len[i]=i;
for(j=i<<1;j<=n;j+=i)
{
if(minus(a[j],a[j-i]*pow1[i]%P)^a[i]) break;
len[j]=i;
}
}
memset(cnt,0,sizeof cnt),
cnt[s[T][n]-97]=1,tot=1;
for(i=n-1;i;--i)
{
lim=i/len[i];
if(id[lim]) for(j=0;j<fac[id[lim]].size();++j)
{
k=fac[id[lim]][j],
ans+=(len[i]<<1<=i?k/2*f[len[i]<<1][tot]:0)+(k&1?f[len[i]][tot]:0)-(b[k*len[i]]<=tot);
if(lim==k*k) break;
k=lim/k,
ans+=(len[i]<<1<=i?k/2*f[len[i]<<1][tot]:0)+(k&1?f[len[i]][tot]:0)-(b[k*len[i]]<=tot);
}
else for(j=1;j<=siz[lim];++j)
{
k=fa[lim][j],
ans+=(len[i]<<1<=i?k/2*f[len[i]<<1][tot]:0)+(k&1?f[len[i]][tot]:0)-(b[k*len[i]]<=tot);
if(lim==k*k) break;
k=lim/k,
ans+=(len[i]<<1<=i?k/2*f[len[i]<<1][tot]:0)+(k&1?f[len[i]][tot]:0)-(b[k*len[i]]<=tot);
}
(cnt[s[T][i]-97]^=1)?++tot:--tot;
}
printf("%lld\n",ans);
}
return 0;
}
优化3
优化2 说明先枚举倍数再枚举约数是 不可行 的,因为 vector 什么的有巨大的常数。
那我们就反过来吧!
大改后,在OJ上测出了 96 96 96 分的好成绩。
但是我还要再优化。
优化4
听说自然溢出是不会WA的,只要把哈希改为 27 27 27 进制数就行,遂改之。
然后换了那个更长的火车头,但是极限数据还是1000多ms。
接着尝试了很多方法,都不可行。
循环展开是不可行的,因为要进行很多次 i+1
,i+2
之类的运算,效率没有太大变化,甚至会更慢。
卡了一个多小时后,突然发现我有两个 0 or 1 → 25 0 \operatorname{or} 1\to 25 0or1→25 的循环,第一个是
fo(j,0,25) f[i][j]=f[i-1][j];
第二个是
fo(j,1,25) f[i][j]+=f[i][j-1];
我先完全展开了第二个循环,发现快了 120 ms 120\operatorname{ms} 120ms !!!
再展开第一个循环,发现又回到了 1000 ms 1000\operatorname{ms} 1000ms 了???
那就只展开第二个循环吧!
后来发现在其它OJ上不用火车头也能过。
代码
#pragma GCC optimize(3)
#pragma GCC optimize("-fgcse")
#pragma GCC optimize("-fgcse-lm")
#pragma GCC optimize("-fipa-sra")
#pragma GCC optimize("-ftree-pre")
#pragma GCC optimize("-ftree-vrp")
#pragma GCC optimize("-fpeephole2")
#pragma GCC optimize("-ffast-math")
#pragma GCC optimize("-fsched-spec")
#pragma GCC optimize("unroll-loops")
#pragma GCC optimize("-falign-jumps")
#pragma GCC optimize("-falign-loops")
#pragma GCC optimize("-falign-labels")
#pragma GCC optimize("-fdevirtualize")
#pragma GCC optimize("-fcaller-saves")
#pragma GCC optimize("-fcrossjumping")
#pragma GCC optimize("-fthread-jumps")
#pragma GCC optimize("-funroll-loops")
#pragma GCC optimize("-fwhole-program")
#pragma GCC optimize("-freorder-blocks")
#pragma GCC optimize("-fschedule-insns")
#pragma GCC optimize("inline-functions")
#pragma GCC optimize("-ftree-tail-merge")
#pragma GCC optimize("-fschedule-insns2")
#pragma GCC optimize("-fstrict-aliasing")
#pragma GCC optimize("-fstrict-overflow")
#pragma GCC optimize("-falign-functions")
#pragma GCC optimize("-fcse-skip-blocks")
#pragma GCC optimize("-fcse-follow-jumps")
#pragma GCC optimize("-fsched-interblock")
#pragma GCC optimize("-fpartial-inlining")
#pragma GCC optimize("no-stack-protector")
#pragma GCC optimize("-freorder-functions")
#pragma GCC optimize("-findirect-inlining")
#pragma GCC optimize("-fhoist-adjacent-loads")
#pragma GCC optimize("-frerun-cse-after-loop")
#pragma GCC optimize("inline-small-functions")
#pragma GCC optimize("-finline-small-functions")
#pragma GCC optimize("-ftree-switch-conversion")
#pragma GCC optimize("-foptimize-sibling-calls")
#pragma GCC optimize("-fexpensive-optimizations")
#pragma GCC optimize("-funsafe-loop-optimizations")
#pragma GCC optimize("inline-functions-called-once")
#pragma GCC optimize("-fdelete-null-pointer-checks")
#include<cstring>
#include<cstdio>
using namespace std;
typedef unsigned long long llu;
typedef long long ll;
#define N 1048577
const int maxn=1048576;
ll f[N][26];
int b[N],F[N],len[N],s[N];
char cnt[26],buf[100005],*l=buf,*r=buf;
llu pow26[N],a[N];
inline char gc()
{return l==r&&(r=(l=buf)+fread(buf,1,100005,stdin),l==r)?EOF:*l++;}
inline void read1(int &k)
{
int ch;while(ch=gc(),ch<48||ch>57);k=ch-48;
while(ch=gc(),ch>=48&&ch<=57) k=k*10+ch-48;
}
inline int read2()
{
int ch;int len=0;
while(ch=gc(),ch<97||ch>122);s[++len]=ch-97;
while(ch=gc(),ch>=97&&ch<=122) s[++len]=ch-97;
return len;
}
int main()
{
freopen("string.in","r",stdin);
freopen("string.out","w",stdout);
llu ans;register int i,j,k;
int T,t,n,tot;
read1(t),pow26[0]=1;
for(i=1;i<=maxn;++i) pow26[i]=27llu*pow26[i-1];
for(T=0;T<t;++T)
{
ans=0,tot=0,n=read2(),
memset(len,0,sizeof len),
memset(cnt,0,sizeof cnt);
for(i=1;i<=n;++i) a[i]=27llu*a[i-1]+s[i];
for(i=1;i<=n;++i)
{
b[i]=(cnt[s[i]]^=1)?++tot:--tot;
for(j=0;j<=25;++j) f[i][j]=f[i-1][j];
++f[i][tot];
}
for(i=1;i<=n;++i)
{
f[i][1]+=f[i][0],
f[i][2]+=f[i][1],
f[i][3]+=f[i][2],
f[i][4]+=f[i][3],
f[i][5]+=f[i][4],
f[i][6]+=f[i][5],
f[i][7]+=f[i][6],
f[i][8]+=f[i][7],
f[i][9]+=f[i][8],
f[i][10]+=f[i][9],
f[i][11]+=f[i][10],
f[i][12]+=f[i][11],
f[i][13]+=f[i][12],
f[i][14]+=f[i][13],
f[i][15]+=f[i][14],
f[i][16]+=f[i][15],
f[i][17]+=f[i][16],
f[i][18]+=f[i][17],
f[i][19]+=f[i][18],
f[i][20]+=f[i][19],
f[i][21]+=f[i][20],
f[i][22]+=f[i][21],
f[i][23]+=f[i][22],
f[i][24]+=f[i][23],
f[i][25]+=f[i][24];
}
for(i=1;i<=n;++i) if(!len[i])
{
len[i]=i;
for(j=i<<1;j<=n&&a[j]-a[j-i]*pow26[i]==a[i];j+=i)
len[j]=i;
}
memset(cnt,0,sizeof cnt),tot=0;
for(i=n;i;--i) F[i]=(cnt[s[i]]^=1)?++tot:--tot;
for(i=1;i<=n;++i)
for(j=i;j<n&&len[j]==i;j+=i)
for(k=j;k<n&&len[k]==i;k+=j)
ans+=f[j][F[k+1]]-(b[j]<=F[k+1]);
printf("%llu\n",ans);
}
return 0;
}