zoukankan      html  css  js  c++  java
  • POJ 1743 Musical Theme(后缀数组+二分答案)

    题意

    长度为 (n) 由数字构成的串,求一段最长满足以下要求的子串:

    • 长度至少为 (5)
    • 在其他位置有一个不相交的等长子串满足原串或原串加上或减去一个数后与之完全相同。

    (1leq n leq 20000)

    思路

    不难看出题目要求的是满足两两数之差相同的串,那么直接对原串进行差分,剩下的就是求两个串至少间隔为 (1) 的相等串的最长长度。这显然是满足单调性的,那么二分这个长度,然后扫后缀数组,对于每一个 (H) 大于等于这个长度的区间,看一看有没有符合要求的串,具体实现看代码。

    代码

    #include<iostream>
    #include<cmath>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<algorithm>
    #define FOR(i,x,y) for(int i=(x),i##END=(y);i<=i##END;++i)
    #define DOR(i,x,y) for(int i=(x),i##END=(y);i>=i##END;--i)
    template<typename T,typename _T>inline bool chk_min(T &x,const _T y){return y<x?x=y,1:0;}
    template<typename T,typename _T>inline bool chk_max(T &x,const _T y){return x<y?x=y,1:0;}
    typedef long long ll;
    const int N=2e4+5;
    int sa[N],rk[N],H[N],tmp[3][N];
    int s[N];
    int n;
    
    void get_SA(int *s,int n,int m)
    {
    	int *x=tmp[0],*y=tmp[1],*c=tmp[2];
    	x[n+1]=y[n+1]=0;
    	FOR(i,1,m)c[i]=0;
    	FOR(i,1,n)c[x[i]=s[i]]++;
    	FOR(i,2,m)c[i]+=c[i-1];
    	DOR(i,n,1)sa[c[x[i]]--]=i;
    	for(int k=1;k<=n;k<<=1)
    	{
    		int p=0;
    		FOR(i,n-k+1,n)y[++p]=i;
    		FOR(i,1,n)if(sa[i]>k)y[++p]=sa[i]-k;
    		FOR(i,1,m)c[i]=0;
    		FOR(i,1,n)c[x[y[i]]]++;
    		FOR(i,2,m)c[i]+=c[i-1];
    		DOR(i,n,1)sa[c[x[y[i]]]--]=y[i];
    		std::swap(x,y);
    		p=x[sa[1]]=1;
    		FOR(i,2,n)x[sa[i]]=y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k]?p:++p;
    		if(p==n)break;
    		m=p;
    	}
    	FOR(i,1,n)rk[sa[i]]=i;
    	int k=0;
    	FOR(i,1,n)
    	{
    		if(k)k--;
    		if(rk[i]==1)continue;
    		int j=sa[rk[i]-1];
    		while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k])k++;
    		H[rk[i]]=k;
    	}
    }
    
    bool check(int k)
    {
    	int Mx=-1e9,Mi=1e9;
    	FOR(i,1,n)
    	{
    		chk_max(Mx,sa[i]),chk_min(Mi,sa[i]);
    		if(Mx-Mi>=k+1)return true;
    		if(H[i+1]<k)Mx=-1e9,Mi=1e9;
    	}
    	return false;
    }
    
    int main()
    {
    	while(scanf("%d",&n),n)
    	{
    		FOR(i,1,n)scanf("%d",&s[i]);
    		FOR(i,1,n-1)s[i]=s[i+1]-s[i]+100;
    		n--;
    		get_SA(s,n,200);
    		int l=3,r=n;
    		while(l<r)
    		{
    			int mid=(l+r+1)>>1;
    			if(check(mid))
    				l=mid;
    			else r=mid-1;
    		}
    		printf("%d
    ",l>=4?l+1:0);
    	}
    	return 0;
    }
    
  • 相关阅读:
    【字符串哈希】The 16th UESTC Programming Contest Preliminary F
    【推导】The 16th UESTC Programming Contest Preliminary L
    【推导】zoj3846 GCD Reduce
    【spfa】【动态规划】zoj3847 Collect Chars
    【搜索】【组合数学】zoj3841 Card
    【贪心】【字典树】Gym
    【贪心】【后缀自动机】Gym
    【拉格朗日插值法】【找规律】【高精度】Gym
    【二分】【动态规划】Gym
    【软件开发综合实验】文本压缩软件
  • 原文地址:https://www.cnblogs.com/Paulliant/p/10573130.html
Copyright © 2011-2022 走看看