zoukankan      html  css  js  c++  java
  • 【POJ1743】Musical Theme-后缀数组+二分答案

    测试地址:Musical Theme

    题目大意:一段旋律有N个音符,音符为1~88之间的一个整数,我们规定旋律的主题为旋律中一个连续的子串,且满足以下要求:1.至少包含5个音符;2.在旋律的另外一个地方也出现(可能会变调,即每一个音符都加上同一个整数);3.和另外一个出现的地方不重合(即两个子串没有相交部分,如[1,2]和[3,4]不重合,[1,2]和[2,3]重合)。给你一段旋律,求出旋律中最长的主题的长度,如果没有主题则输出0。

    做法:看上去非常高大上啊,好久没写后缀数组手生了,活活写了3h才过,要命啊......一开始没有看懂题目的意思,以为就是求两个不重和的相等子串,结果手测数据发现不对劲,还是看了discuss才发现还有变调这一说......以后要注意......

    行了不说废话了,我们知道一个主题无论怎么变调,音符两两之间的差值都是不变的,所以我们先给相邻的两个音符做差,形成新的数列(长度为N-1),给差值从前往后标号为1~N-1,可以知道这个新的数列上的每一个连续子串[i,j]都唯一对应着原来的一段旋律[i,j+1]。如果在差值数列上有两个子串相等,那么这两个子串所对应的原数列上的两段就是通过变调形成的,那么问题就转化为求差值数列上不重合的两个相等子串。由于题目要求最长的主题,我们可以知道答案是单调的,所以我们可以二分主题长度,问题变为判定性问题。要注意的是,在原数列上长度为k的一段,在差值数列上长度为k-1,我们接下来要二分的是差值数列上的长度。

    我们对差值数列跑一遍后缀数组,求出height数组。然后二分长度k,根据height数组的值给后缀分组,保证每一组内的后缀两两之间的LCP不小于k,我们知道这样分组的话,每一组后缀在SA上都是一个连续的区间。如果存在长为k的不重合相等子串,充要条件是存在同一组内的两个前缀起点的差值大于等于k,只不过这里有一点要注意,差值数列上不重合的子串,它们对应的原数列中的子串不一定不重合,例如差值数列上[1,2]和[3,4]不重合,然而对应原数列的[1,3]和[3,5]就重合了,所以这里起点差值要大于k才合法。如果合法则说明长为k的不重合相等子串存在,如果每一组都不存在这样的后缀则说明不存在。

    最后先把二分出的答案+1,如果小于5则输出0,否则直接输出答案即可。

    以下是本人(奇丑无比)的代码(为了节约空间,求后缀数组的过程中不同步骤中相同数组的意义不一定相同,所以才说丑(摔)):

    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    int n,a[20010],x[50010],y[20010],s[20010],height[20010];
    int t[20010],rank[20010],sa[20010];
    
    void DA() //倍增求rank和SA数组
    {
      int p=1,up=180;
      memset(x,0,sizeof(x));
      for(int i=1;i<=n;i++) x[i]=a[i];
      while(p<n)
      {
    	memset(t,0,sizeof(t));
        for(int i=1;i<=n;i++) y[i]=x[i+p]; //赋值第二关键字
    	
    	for(int i=1;i<=n;i++) t[y[i]]++;
    	for(int i=1;i<=up;i++) t[i]+=t[i-1];
    	for(int i=up;i>=1;i--) t[i]=t[i-1];
    	t[0]=0;
    	for(int i=1;i<=n;i++) s[++t[y[i]]]=i; //第一次基数排序
    	
    	memset(t,0,sizeof(t));
    	for(int i=1;i<=n;i++) t[x[s[i]]]++;
    	for(int i=1;i<=up;i++) t[i]+=t[i-1];
    	for(int i=up;i>=1;i--) t[i]=t[i-1];
    	for(int i=1;i<=n;i++) y[++t[x[s[i]]]]=s[i]; //第二次基数排序
    	
    	up=1;
    	s[y[1]]=1;
    	for(int i=2;i<=n;i++)
    	{
    	  if (x[y[i]]!=x[y[i-1]]||x[y[i]+p]!=x[y[i-1]+p]) up++;
    	  s[y[i]]=up;
    	}
    	for(int i=1;i<=n;i++) x[i]=s[i];
    	p<<=1;
      }
      for(int i=1;i<=n;i++)
      {
        rank[i]=x[i];
        sa[rank[i]]=i;
      }
    }
    
    void calc_height() //求height数组
    {
      for(int i=1,j=0;i<=n;i++)
      {
        if (rank[i]==1) continue;
    	while(a[i+j]==a[sa[rank[i]-1]+j]) j++;
    	height[rank[i]]=j;
    	if (j>0) j--;
      }
    }
    
    bool check(int k) //检查是否存在长为k的不重合相等子串
    {
      int mx=0,mn=1000000000;
      for(int i=1;i<=n;i++)
      {
        mx=max(mx,sa[i]);
    	mn=min(mn,sa[i]);
    	if (height[i+1]<k)
    	{
    	  if (mx-mn>k) return 1;
    	  mx=0,mn=1000000000;
    	}
      }
      return 0;
    }
    
    int main()
    {
      while(scanf("%d",&n)&&n)
      {
        for(int i=1;i<=n;i++)
    	{
    	  scanf("%d",&a[i]);
    	  if (i>1) a[i-1]=a[i]-a[i-1]+88;
    	}
    	a[n]=height[n]=0;
    	n--;
    	DA();
    	calc_height();
    	
    	int l=4,r=n-1,ans=0;
    	while(l<=r)
    	{
    	  int mid=(l+r)>>1;
    	  if (check(mid))
    	  {
    	    ans=max(ans,mid);
    	    l=mid+1;
    	  }
    	  else r=mid-1;
    	}
    	printf("%d
    ",ans?ans+1:0);
      }
      
      return 0;
    }
    


  • 相关阅读:
    利用for循环 修改精灵图背景位置
    添加列表项 避免浏览器反复渲染 Fragment
    向元素添加属性名和属性值
    分割文本节点
    查询、回显 基本功能
    获取注释
    合并文本节点
    Node(节点)的4个操作方法
    setTimeout与setInterval
    javascript循环
  • 原文地址:https://www.cnblogs.com/Maxwei-wzj/p/9793774.html
Copyright © 2011-2022 走看看