poj_1743 后缀数组

题目大意

给定一串数字,长度为N。定义数字中的某个连续的子串为一个"theme",只要子串满足: 
(1)长度 >= 5 
(2)和该子串相同或者该子串的“变种串”在整串数字中出现次数大于1 
(3)假设整串中有k个该子串及其“变种串”,那么其中至少有两个不相重叠 
    求满足要求的 "theme" 串的最长长度。

题目分析

(1)首先考虑将“变种”串和原子串相互比较的问题,对字符串中所有索引大于等于1的字符都用该字符减去前一个字符,这样得到串的差串之后,原theme和其“变种”就一样了,此时只需要求差串中的最长相同子串,且这些子串之间不重叠

求最长相同子串,可以考虑使用后缀数组和height数组。显然,height越大,则两个子串的公共前缀越长,越有可能是最长相同子串。但是,题目对"theme"串的要求(3)至少两个不重叠,因此需要考虑height[i]在尽可能大的同时,保证SA[i]和SA[i-1]之间的差值要大于height[i]以保证不重叠

(2)然后,试图求解是否存在长度为M的"theme"串。 
容易看出,后缀Suffix(j)和Suffix(k)的最长公共前缀的长度为 height[rank[j]+1], height[rank[j]+2]...height[rank[k]]的最小值。i从1到N遍历,通过height[i]>=M将i分开,即将后缀分成若干组,每组中的后缀的公共前缀长度均大于等于M,且可以肯定组A中的某后缀t1和组B中的某后缀t2的公共前缀长度小于M。若存在这样的组,则可以确定找到了公共前缀大于等于M的子串,下一步需要确定这些子串不重叠。只需要在组内寻找 SA[i] 之间最大的查看,看是否大于子串的长度,若大于则可以确定不重叠。

(3)最后,求解"theme"串长度M的最大值,用二分法对"theme"串的可能长度进行二分求解,长度范围为0到N。每次二分得到中值M,先判断能否找到长度为k的"theme"串,若不能,则减小M,否则增加M。直到找到长度M最大的"theme"串。

实现(c++)

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#define MAX(a, b) a>b? a:b
#define MAX_ARRAY_SIZE 20005
#define LETTERS 10000

int gStrLen;
int gStr[MAX_ARRAY_SIZE];
int gCount[MAX_ARRAY_SIZE];
int gSuffixArray[MAX_ARRAY_SIZE];
int gRank[MAX_ARRAY_SIZE];
int gOrderBySecondKey[MAX_ARRAY_SIZE];
int gFirstKeyArray[MAX_ARRAY_SIZE];
int gHeight[MAX_ARRAY_SIZE];

bool Compare(int* arr, int a, int b, int step){
	return arr[a] == arr[b] && arr[a + step] == arr[b + step];
}

void GetStr(char* str){
	memset(gStr, 0, sizeof(gStr));
	gStrLen = strlen(str);
	for (int i = 0; i < gStrLen; i++){
		gStr[i] = str[i] - ‘a‘ + 1;
	}
	gStr[gStrLen++] = 0;
}

void GetSuffixArray(){
	int n = gStrLen;
	memset(gCount, 0, sizeof(gCount));
	for (int i = 0; i < n; i++){
		gRank[i] = gStr[i];
		gCount[gRank[i]] ++;
	}
	for (int i = 1; i < LETTERS; i++){
		gCount[i] += gCount[i - 1];
	}
	for (int i = n - 1; i >= 0; i--){
		gSuffixArray[--gCount[gRank[i]]] = i;
	}
	int step = 1;
	int* rank = gRank, *order_by_second_key = gOrderBySecondKey;
	int m = LETTERS;
	while (step < n){
		int p = 0;
		for (int i = n - step; i < n; i++){
			order_by_second_key[p++] = i;
		}
		for (int i = 0; i < n; i++){
			if (gSuffixArray[i] >= step){
				order_by_second_key[p++] = gSuffixArray[i] - step;
			}
		}
		for (int i = 0; i < n; i++){
			gFirstKeyArray[i] = rank[order_by_second_key[i]];
		}
		for (int i = 0; i < m; i++){
			gCount[i] = 0;
		}
		for (int i = 0; i < n; i++){
			gCount[gFirstKeyArray[i]] ++;
		}
		for (int i = 1; i < m; i++){
			gCount[i] += gCount[i - 1];
		}
		for (int i = n - 1; i >= 0; i--){
			gSuffixArray[--gCount[gFirstKeyArray[i]]] = order_by_second_key[i];
		}
		int* tmp = rank;
		rank = order_by_second_key;
		order_by_second_key = tmp;
		rank[gSuffixArray[0]] = 0;
		p = 0;
		for (int i = 1; i < n; i++){
			if (Compare(order_by_second_key, gSuffixArray[i], gSuffixArray[i - 1], step)){
				rank[gSuffixArray[i]] = p;
			}
			else{
				rank[gSuffixArray[i]] = ++p;
			}
		}
		m = p + 1;
		step *= 2;
	}
}

void GetHeight(){
	int n = gStrLen;
	for (int i = 1; i < n; i++){
		gRank[gSuffixArray[i]] = i;
	}
	int k = 0, j;
	gHeight[0] = 0;
	for (int i = 0; i < n - 1; i++){
		j = gSuffixArray[gRank[i] - 1];
		if (k){
			k--;
		}
		while (i + k < n && j + k < n && gStr[i + k] == gStr[j + k]){
			k++;
		}
		gHeight[gRank[i]] = k;
	}
}
bool Find(int k){
	int end = 1;
	int min_pos, max_pos;
	while (end < gStrLen){
		max_pos = min_pos = gSuffixArray[end-1];
		while (end < gStrLen && gHeight[end] >= k - 1){
			if (min_pos > gSuffixArray[end]){
				min_pos = gSuffixArray[end];
			}
			if (max_pos < gSuffixArray[end]){
				max_pos = gSuffixArray[end];
			}

			end ++;
		}
		if (max_pos - min_pos >= k){
			return true;
		}
		end ++;
	}
	return false;
}
void printstr(int n){
	printf("string = \n");
	for (int i = 0; i < n; i++){
		printf("%d ", gStr[i]);
	}
	printf("\n");
}
void printsuffix(int n){
	printf("suffix = \n");
	for (int i = 0; i < n; i++){
		printf("%d ", gSuffixArray[i]);
	}
	printf("\n");
}
void printheigt(int n){
	printf("height = \n");
	for (int i = 0; i < n; i++){
		printf("%d ", gHeight[i]);
	}
	printf("\n");
}
int main(){
	int n;
	while (true){
		scanf("%d", &n);

		if (n == 0){
			break;
		}

		for (int i = 0; i < n; i++){
			scanf("%d", &gStr[i]);
		}
		int min = 100;
		for (int i = 1; i < n; i++){
			gStr[i - 1] = gStr[i] - gStr[i - 1];
			min = gStr[i - 1] < min ? gStr[i - 1] : min;
		}
		min--;
		for (int i = 0; i < n; i++){
			gStr[i] -= min;
		}
		gStr[n-1] = 0;
		gStrLen = n;

		GetSuffixArray();
		GetHeight();
//		printstr(n);
//		printsuffix(n);
//		printheigt(n);
		int beg = 0, end = n, mid, max;
		bool flag = true;
		while (beg < end){
			mid = (beg + end) / 2;
			if (Find(mid)){
				beg = mid + 1;
				max = mid;
			}
			else{
				if (mid <= 5){
					flag = false;
					break;
				}
				end = mid;
			}
		}
		if (!flag){
			printf("0\n");
		}
		else{
			printf("%d\n", max);
		}
	}
	return 0;
}
时间: 2024-10-14 10:16:06

poj_1743 后缀数组的相关文章

SPOJ 705 Distinct Substrings(后缀数组)

[题目链接] http://www.spoj.com/problems/SUBST1/ [题目大意] 给出一个串,求出不相同的子串的个数. [题解] 对原串做一遍后缀数组,按照后缀的名次进行遍历, 每个后缀对答案的贡献为n-sa[i]+1-h[i], 因为排名相邻的后缀一定是公共前缀最长的, 那么就可以有效地通过LCP去除重复计算的子串. [代码] #include <cstdio> #include <cstring> #include <algorithm> usi

hdu5769--Substring(后缀数组)

题意:求含有某个字母的某个字符串的不同子串的个数 题解:后缀数组,记录每个位置距离需要出现的字母的距离就可以了.因为不太了解后缀模版卡了一会,还是很简单的. 记住sa和height数组都是1-n的下标. //后缀数组 #include <stdio.h> #include <cstring> #include <iostream> #include <algorithm> using namespace std; typedef long long ll;

hdu 3518 Boring counting 后缀数组LCP

题目链接 题意:给定长度为n(n <= 1000)的只含小写字母的字符串,问字符串子串不重叠出现最少两次的不同子串个数; input: aaaa ababcabb aaaaaa # output 2 3 3 思路:套用后缀数组求解出sa数组和height数组,之后枚举后缀的公共前缀长度i,由于不能重叠,所以计数的是相邻height不满足LCP >= i的. 写写对后缀数组倍增算法的理解: 1.如果要sa数组对应的值也是1~n就需要在最后加上一个最小的且不出现的字符'#',里面y[]是利用sa数

【tyvj1860】后缀数组

描述 我们定义一个字符串的后缀suffix(i)表示从s[i]到s[length(s)]这段子串.后缀数组(Suffix array)SA[i]中存放着一个排列,满足suffix(sa[i])<suffix(sa[i+1]) 按照字典序方式比较定义height[i]表示suffix(sa[i])与suffix(sa[i-1])之间的最长公共前缀长度,其中height[1]=0你的任务就是求出SA和height这两个数组.字符串长度<=200000 输入格式 一行,为描述中的字符串(仅会出现小写

BZOJ 3238 AHOI 2013 差异 后缀数组+单调栈

题目大意: 思路:一看各种后缀那就是后缀数组没跑了. 求出sa,height之后就可以乱搞了.对于height数组中的一个值,height[i]来说,这个值能够作为lcp值的作用域只在左边第一个比他小的位置到右边第一个比他小的位置.这个东西很明显可以倍增RMQ+二分/单调栈. 之后就是数学题了 Σlen[Ti] + len[Tj] = (len + 1) * len * (len - 1),之后吧所有求出来的Σ2 * lcp(Ti,Tj)减掉就是答案. 记得答案开long long CODE:

hdu 5030 Rabbit&#39;s String(后缀数组&amp;二分)

Rabbit's String Time Limit: 40000/20000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) Total Submission(s): 288    Accepted Submission(s): 108 Problem Description Long long ago, there lived a lot of rabbits in the forest. One day, the

hdu 4416 Good Article Good sentence(后缀数组&amp;思维)

Good Article Good sentence Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 2308    Accepted Submission(s): 649 Problem Description In middle school, teachers used to encourage us to pick up pre

uva 10829 - L-Gap Substrings(后缀数组)

题目链接:uva 10829 - L-Gap Substrings 题目大意:给定一个字符串,问有多少字符串满足UVU的形式,要求U非空,V的长度为g. 解题思路:对字符串的正序和逆序构建后缀数组,然后枚举U的长度l,每次以长度l分区间,在l和l+d+g所在的两个区间上确定U的最大长度. #include <cstdio> #include <cstring> #include <cstdlib> #include <algorithm> using nam

poj 3693 Maximum repetition substring(后缀数组)

题目链接:poj 3693 Maximum repetition substring 题目大意:求一个字符串中循环子串次数最多的子串. 解题思路:对字符串构建后缀数组,然后枚举循环长度,分区间确定.对于一个长度l,每次求出i和i+l的LCP,那么以i为起点,循环子串长度为l的子串的循环次数为LCP/l+1,然后再考虑一下从i-l+1~i之间有没有存在增长的可能性. #include <cstdio> #include <cstring> #include <vector>