Description
T组数据,
T
≤
10000
,
∑
n
≤
300000
T\leq 10000,\sum n\leq 300000
T≤10000,∑n≤300000
Solution
先考虑怎么把这些平方串弄出来
这似乎是一个很经典的套路了(WC2019的时候好像讲了)
枚举平方串的长度为2L,那么我们在L,2L,3L…的位置设置关键点,用SA或者二分+哈希求出相邻关键点的最长公共前缀和最长公共后缀,这样对于每一对相邻的关键点就有一段合法的平方串起始区间。根据调和级数的原理,总的关键点数是
O
(
n
log
n
)
O(n\log n)
O(nlogn)的,总的区间数也是
O
(
n
log
n
)
O(n\log n)
O(nlogn)的
现在要做出这个最小生成森林,模拟Kruskal的过程,将L按权值排序,我们相当于要做下面这个东西:
对于一段区间
[
l
,
r
]
[l,r]
[l,r],需要将所有的
x
∈
[
l
,
r
]
x\in[l,r]
x∈[l,r]向
x
+
L
x+L
x+L连边,如果某次连边连通了两个不同的集合则需要将答案加上
w
[
L
]
w[L]
w[L]
现在的问题是,如果直接用一个并查集维护,我们将会有大量重复的连边,时间复杂度是不对的。
也就是说,我们需要快速的判定一段区间是否已经连过边,并且及时的在发现已经连过时退出。
既然一个并查集不行,我们就用log个并查集
类似RMQ的思想,我们弄出
l
o
g
log
log层并查集,若
x
,
y
x,y
x,y在第i层连通,表示所有的
0
≤
j
<
2
i
,
x
+
j
0\leq j<2^i,x+j
0≤j<2i,x+j与
y
+
j
y+j
y+j都是连通的。
那么我们合并一段区间,可以类似RMQ时拆成两个并查集上的合并,如果当前要连的已经连通了,说明更小的层肯定已经也连起来的,那就直接退出,否则递归到更下面的层去连。
计算答案的时候只在长度为
2
0
2^0
20的层计算。
分析复杂度,由于每一层最后最多连成一棵生成树,也就是每一层最多连 n − 1 n-1 n−1条边,总共log层,已经连通的失败次数总数是区间个数 n log n n\log n nlogn的。因此总的时间复杂度是 n α ( n ) log n n\alpha(n)\log n nα(n)logn
Code
#include <bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fod(i,a,b) for(int i=a;i>=b;--i)
#define N 300005
#define LL long long
using namespace std;
int a[N],w[N],n,m,n1,d[N],f[20][N],cnt,l2[N];
LL cf[N],ans;
struct Suffix_Array
{
int SA[N],rank[N],r1[N],s2[N],ct[N],hi[N],a[N],rq[20][N];
void build()
{
memset(ct,0,n+1<<2);
fo(i,1,n) ct[rank[i]=a[i]]++;
fo(i,1,n) ct[i]+=ct[i-1];
fod(i,n,1) SA[ct[rank[i]]--]=i;
int mx=n;
for(int j=1,k=0;k<n;j<<=1,mx=k)
{
int p=0;
fo(i,n-j+1,n) s2[++p]=i;
fo(i,1,n) if(SA[i]>j) s2[++p]=SA[i]-j;
memset(ct,0,n+1<<2);
fo(i,1,n) ct[rank[s2[i]]]++;
fo(i,1,mx) ct[i]+=ct[i-1];
fod(i,n,1) SA[ct[rank[s2[i]]]--]=s2[i];
k=1,r1[SA[1]]=1;
fo(i,2,n) r1[SA[i]]=(rank[SA[i]]==rank[SA[i-1]]&&rank[SA[i]+j]==rank[SA[i-1]+j])?k:++k;
fo(i,1,n) rank[i]=r1[i];
}
hi[1]=0;
fo(i,1,n)
{
if(rank[i]==1) continue;
int j=max(hi[rank[i-1]]-1,0);
while(a[i+j]==a[SA[rank[i]-1]+j]&&i+j<=n&&SA[rank[i]-1]+j<=n) j++;
hi[rank[i]]=j;
}
}
void rmq()
{
fo(i,1,n) rq[0][i]=hi[i];
fo(j,1,18)
fo(i,1,n) rq[j][i]=(i+(1<<j-1)>n||rq[j-1][i]<=rq[j-1][i+(1<<j-1)])?rq[j-1][i]:rq[j-1][i+(1<<j-1)];
}
int lcp(int x,int y)
{
int rx=rank[x],ry=rank[y];
if(rx>ry) swap(rx,ry);rx++;
int num=l2[ry-rx+1];
return min(rq[num][rx],rq[num][ry-(1<<num)+1]);
}
void cl()
{
memset(SA,0,n+1<<2);
memset(hi,0,n+1<<2);
memset(rank,0,n+1<<2);
}
}S,RS;
bool cmp(int x,int y)
{
return w[x]<w[y];
}
int getf(int k,int p)
{
return (!f[p][k]||f[p][k]==k)?k:f[p][k]=getf(f[p][k],p);
}
void merge(int x,int y,int p)
{
int fx=getf(x,p),fy=getf(y,p);
if(fx==fy) return;
f[p][fx]=fy;
if(p==0) ans+=w[y-x];
else merge(x,y,p-1),merge(x+(1<<p-1),y+(1<<p-1),p-1);
}
int main()
{
int t;
cin>>t;
cf[0]=1;
fo(i,1,18) l2[1<<i]=i;
fo(i,2,300000) if(!l2[i]) l2[i]=l2[i-1];
int cp=0;
while(t--)
{
scanf("%d",&n);
cnt=0;
int ct=0;
cp++;
fo(i,1,n)
{
scanf("%d",&a[i]);
S.a[i]=a[i],RS.a[n-i+1]=a[i];
fo(j,0,18) f[j][i]=0;
}
S.build(),RS.build();
S.rmq(),RS.rmq();
m=n/2;
fo(i,1,m) scanf("%d",&w[i]),d[i]=i;
sort(d+1,d+m+1,cmp);
ans=0;
fo(l1,1,m)
{
int l=d[l1];
for(int i=l;i+l<=n;i+=l)
{
int x=min(l,RS.lcp(n-i+1,n-i-l+1)),y=min(l,S.lcp(i,i+l));
if(x+y-1>=l)
{
int p=i-x+1,q=i+y-l;q=q+l-1;
int num=l2[q-p+1];
merge(p,p+l,num),merge(q-(1<<num)+1,q-(1<<num)+1+l,num);
}
}
}
S.cl(),RS.cl();
printf("%lld\n",ans);
}
}