[JZOJ6152] Endless【并查集】【SA】【ST表】

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hzj1054689699/article/details/89683371

Description

在这里插入图片描述
T组数据, T 10000 , n 300000 T\leq 10000,\sum n\leq 300000

Solution

先考虑怎么把这些平方串弄出来

这似乎是一个很经典的套路了(WC2019的时候好像讲了)
枚举平方串的长度为2L,那么我们在L,2L,3L…的位置设置关键点,用SA或者二分+哈希求出相邻关键点的最长公共前缀和最长公共后缀,这样对于每一对相邻的关键点就有一段合法的平方串起始区间。根据调和级数的原理,总的关键点数是 O ( n log n ) O(n\log n) 的,总的区间数也是 O ( n log n ) O(n\log n)

现在要做出这个最小生成森林,模拟Kruskal的过程,将L按权值排序,我们相当于要做下面这个东西:
对于一段区间 [ l , r ] [l,r] ,需要将所有的 x [ l , r ] x\in[l,r] x + L x+L 连边,如果某次连边连通了两个不同的集合则需要将答案加上 w [ L ] w[L]

现在的问题是,如果直接用一个并查集维护,我们将会有大量重复的连边,时间复杂度是不对的。
也就是说,我们需要快速的判定一段区间是否已经连过边,并且及时的在发现已经连过时退出。

既然一个并查集不行,我们就用log个并查集
类似RMQ的思想,我们弄出 l o g log 层并查集,若 x , y x,y 在第i层连通,表示所有的 0 j < 2 i , x + j 0\leq j<2^i,x+j y + j y+j 都是连通的。

那么我们合并一段区间,可以类似RMQ时拆成两个并查集上的合并,如果当前要连的已经连通了,说明更小的层肯定已经也连起来的,那就直接退出,否则递归到更下面的层去连。
计算答案的时候只在长度为 2 0 2^0 的层计算。

分析复杂度,由于每一层最后最多连成一棵生成树,也就是每一层最多连 n 1 n-1 条边,总共log层,已经连通的失败次数总数是区间个数 n log n n\log n 的。因此总的时间复杂度是 n α ( n ) log n n\alpha(n)\log n

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);
	}
}

猜你喜欢

转载自blog.csdn.net/hzj1054689699/article/details/89683371