• 题解 LOJ2083 「NOI2016」优秀的拆分


    题目链接

    约定:( ext{suf}(i))表示以(i)开头的后缀((s[idots n])),( ext{pre}(i))表示以(i)结尾的前缀((s[1dots i])),( ext{lcp}(s_1,s_2))表示两个串的最长公共前缀,( ext{lcs}(s_1,s_2))表示两个串的最长公共后缀。

    (f[i])表示以(i)为结尾的,( ext{AA})式的子串数量。(g[i])表示以(i)为开头的,( ext{BB})式的子串数量。我们可以枚举( ext{AA})( ext{BB})的分界点,然后求出答案,也就是说,答案等于:

    [sum_{i=1}^{n-1}f[i]cdot g[i+1] ]

    对每个(i),都求一遍(f)(g)。利用哈希或后缀数组判断子串相等。时间复杂度(O(n^2)),期望得(95)分。

    继续优化。(f), (g)是类似的,以求(f)为例。考虑枚举( ext{A})的长度( ext{len})。我们把(1dots n)中所有是( ext{len})的倍数的点,标为“关键点”。发现一个( ext{AA})式的子串,必定跨过恰好(2)个关键点!

    考虑一组相邻的关键点(i,j) (显然,(j=i+ ext{len})),计算跨过(i,j)( ext{AA})的数量。设(x= ext{lcp}( ext{suf}(i), ext{suf}(j)))(y= ext{lcs}( ext{pre}(i-1), ext{pre}(j-1)))。如果(x+y< ext{len}),显然跨过(i,j)( ext{AA})数量为(0)。否则,中间的两段(x), (y)会有一个交,第一个( ext{A})的终点,只要落在这段交上都是可行的,所以会有(x+y- ext{len}+1)个跨过(i,j)( ext{AA}),并且它们的结尾位置,是一段连续的区间,所以对这段区间的(f)(+1)即可,可以用差分实现。

    预处理后缀数组后,求( ext{lcp}), ( ext{lcs})可以(O(1))实现。因为关键点的总数是调和级数的,所以总时间复杂度(O(nlog n))

    参考代码:

    //problem:LOJ2083
    #include <bits/stdc++.h>
    using namespace std;
    
    #define pb push_back
    #define mk make_pair
    #define lob lower_bound
    #define upb upper_bound
    #define fi first
    #define se second
    #define SZ(x) ((int)(x).size())
    
    typedef unsigned int uint;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int,int> pii;
    
    template<typename T>inline void ckmax(T& x,T y){x=(y>x?y:x);}
    template<typename T>inline void ckmin(T& x,T y){x=(y<x?y:x);}
    
    const int MAXN=3e4;
    const int LOG_MAXN=15;
    int n;
    char s[MAXN+5];
    struct SuffixArray{
    	int n,m,x[MAXN+5],y[MAXN+5],c[MAXN+5],sa[MAXN+5];
    	int rk[MAXN+5],ht[MAXN+5],st[MAXN+5][LOG_MAXN+5];
    	int _log2[MAXN+5];
    	void clear(){
    		for(int i=1;i<=max(n,(int)'z');++i)c[i]=0;
    		for(int i=1;i<=n;++i)y[i]=0;
    	}
    	void init(){
    		_log2[0]=-1;
    		for(int i=1;i<=MAXN;++i)_log2[i]=_log2[i>>1]+1;
    	}
    	void build(char* s,int _n){
    		n=_n;
    		m='z';
    		for(int i=1;i<=n;++i)c[x[i]=s[i]]++;
    		for(int i=1;i<=m;++i)c[i]+=c[i-1];
    		for(int i=n;i>=1;--i)sa[c[x[i]]--]=i;
    		for(int k=1;k<=n;k<<=1){
    			int num=0;
    			for(int i=n-k+1;i<=n;++i)y[++num]=i;
    			for(int i=1;i<=n;++i)if(sa[i]>k)y[++num]=sa[i]-k;
    			for(int i=1;i<=m;++i)c[i]=0;
    			for(int i=1;i<=n;++i)c[x[i]]++;
    			for(int i=1;i<=m;++i)c[i]+=c[i-1];
    			for(int i=n;i>=1;--i)sa[c[x[y[i]]]--]=y[i];
    			
    			for(int i=1;i<=n;++i)swap(x[i],y[i]);
    			x[sa[1]]=1;num=1;
    			for(int i=2;i<=n;++i)
    				x[sa[i]]=((y[sa[i]]==y[sa[i-1]] && y[sa[i]+k]==y[sa[i-1]+k])?num:++num);
    			if(num==n)break;
    			m=num;
    		}
    		for(int i=1;i<=n;++i)rk[sa[i]]=i;
    		//for(int i=1;i<=n;++i)cout<<rk[i]<<" ";cout<<endl;
    		int k=0;
    		for(int i=1;i<=n;++i){
    			if(rk[i]==1)continue;
    			if(k)--k;
    			int j=sa[rk[i]-1];
    			while(i+k<=n && j+k<=n && s[i+k]==s[j+k])
    				++k;
    			ht[rk[i]]=k;
    		}
    		for(int i=1;i<=n;++i)st[i][0]=ht[i];
    		for(int j=1;j<=LOG_MAXN;++j){
    			for(int i=1;i+(1<<(j-1))<=n;++i){
    				st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
    			}
    		}
    	}
    	int rmq(int l,int r){
    		int k=_log2[r-l+1];
    		return min(st[l][k],st[r-(1<<k)+1][k]);
    	}
    	int get_lcp(int i,int j){
    		if(i==j)
    			return n-i+1;
    		i=rk[i]; j=rk[j];
    		if(i>j)swap(i,j);
    		assert(i<j);
    		return rmq(i+1,j);
    	}
    	SuffixArray(){}
    };
    
    SuffixArray SA,SA_rev;
    
    int f[MAXN+5],g[MAXN+5];
    
    void solve_case(){
    	SA.clear();
    	SA_rev.clear();
    	cin>>(s+1); n=strlen(s+1);
    	SA.build(s,n);
    	reverse(s+1,s+n+1);
    	SA_rev.build(s,n);
    	
    	for(int i=1;i<=n;++i)f[i]=g[i]=0;
    	
    	int maxlen=(n-2)/2;
    	for(int len=1;len<=maxlen;++len){
    		for(int k=1;(k+1)*len<=n;++k){
    			int i=k*len;
    			int j=(k+1)*len;
    			
    			int lcp=SA.get_lcp(i,j);
    			int lcs=(i==1?0:SA_rev.get_lcp(n-(i-1)+1,n-(j-1)+1));
    			
    			if(!lcp)continue;
    			
    			int l=j-min(lcs,len-1)+len-1;
    			int r=min(n,j+min(lcp,len)-1);
    			if(l<=r){
    				f[l]++;f[r+1]--;
    			}
    			l=max(1,i-min(lcs,len-1));
    			r=i+min(lcp,len)-1-len+1;
    			if(l<=r){
    				g[l]++;g[r+1]--;
    			}
    		}
    	}
    	for(int i=1;i<=n;++i)f[i]+=f[i-1],g[i]+=g[i-1];
    	f[n+1]=g[n+1]=0;
    	ll ans=0;
    	for(int i=1;i<=n-2;++i)
    		ans+=(ll)f[i]*g[i+1];
    	cout<<ans<<endl;
    }
    int main() {
    	SA.init();
    	SA_rev.init();
    	int T;cin>>T;while(T--){
    		solve_case();
    	}
    	return 0;
    }
    
  • 相关阅读:
    滴滴快车奖励政策,高峰奖励,翻倍奖励,按成交率,指派单数分级(4月19日)
    2016年小升初海淀区全部初中排名分析
    LVM Linear vs Striped Logical Volumes
    Spring Data Redis实现消息队列——发布/订阅模式
    Redis Pubsub命令用法
    mysql 截取身份证出生日期
    MA均线组合
    Eclipse代码格式化规范
    JSON Web Token实际应用
    JSON Web Token单点登录设计
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/13336660.html
Copyright © 2020-2023  润新知