后缀数组 - poj3261 Milk Patterns

题目:

http://poj.org/problem?id=3261


题意:

给一个数组,求数组中至少出现k次的最长可重叠子串


思路:

后缀数组可以解决的经典问题之一

先建立后缀数组,然后二分枚举长度,把问题转化为是否存在一个至少出现k次的长度为mid的可重叠子串,这个判定问题可以在O(n)的时间内解决,遍历一次后缀数组即可,乘上二分的时间复杂度O(lgn),O(nlgn)的规模可解决本题。

这里讲一下怎么一次遍历height数组就能判定,将sa序列视为若干组,每组内的任意两个后缀必须满足lcp值大于等于mid,即在height数组中某个区间的值均大于等于mid,注意,这里height数组中的[l,r]区间对应的是,后缀排名[l-1,r]的后缀。因为这分组在height中是一段段连续的区间,所以遍历一次就能实现。如果存在一组满足组内后缀数大于等于k则判定为真,否则为假。


代码:

#include <stdio.h>
#include <iostream>
#include <string.h>
#include <algorithm>
using namespace std;

#define rep(i,n) for(int i = 0; i < n; i++)

const int MAXSIZE = 2*1e5 + 100;

int rk[MAXSIZE], sa[MAXSIZE], height[MAXSIZE], wa[MAXSIZE], res[MAXSIZE];
char w[MAXSIZE];
int len;

void getSa(int up) {
	int *k = rk, *id = height, *r = res, *cnt = wa;
	rep(i, up) cnt[i] = 0;
	rep(i, len) cnt[k[i] = w[i]]++;
	rep(i, up) cnt[i + 1] += cnt[i];
	for (int i = len - 1; i >= 0; i--) {
		sa[--cnt[k[i]]] = i;
	}
	int d = 1, p = 0;
	while (p < len){
		for (int i = len - d; i < len; i++) id[p++] = i;
		rep(i, len)  if (sa[i] >= d) id[p++] = sa[i] - d;
		rep(i, len) r[i] = k[id[i]];
		rep(i, up) cnt[i] = 0;
		rep(i, len) cnt[r[i]]++;
		rep(i, up) cnt[i + 1] += cnt[i];
		for (int i = len - 1; i >= 0; i--) {
			sa[--cnt[r[i]]] = id[i];
		}
		swap(k, r);
		p = 0;
		k[sa[0]] = p++;
		rep(i, len - 1) {
			if (sa[i] + d < len && sa[i + 1] + d < len && r[sa[i]] == r[sa[i + 1]] && r[sa[i] + d] == r[sa[i + 1] + d])
				k[sa[i + 1]] = p - 1;
			else k[sa[i + 1]] = p++;
		}
		if (p >= len) return;
		d <<= 1, up = p, p = 0;
	}
}

void getHeight() {
	int i, k, h = 0;
	rep(i, len) rk[sa[i]] = i;
	rep(i, len) {
		if (rk[i] == 0)
			h = 0;
		else {
			k = sa[rk[i] - 1];
			if (h) h--;
			while (w[i + h] == w[k + h]) h++;
		}
		height[rk[i]] = h;
	}
}

void getSuffix() {
	int up = 0;
	rep(i, len) {
        up = up > w[i] ? up : w[i];
	}
	w[len] = 0;
	getSa(up + 1);
	getHeight();
}

int solve(int k){
    getSuffix();
    int l = 0, r = len;
    while (l<=r){
        int mid = (l+r)>>1;
        bool flag = false;
        int sum = 1;
        for (int i=1;i<len;++i){
            if (height[i]>=mid){
                sum++;
            }
            else {
                if (sum >= k){
                    flag = true;
                    break;
                }
                sum = 1;
            }
        }
        if (sum >= k){
            flag = true;
        }
        if (flag)
            l = mid + 1;
        else
            r = mid - 1;

        //cout<<"mid res: "<<mid<<" "<<flag<<endl;
    }
    return r;
}

int main(){
    int n,k;
    cin>>n>>k;
    for (int i=0;i<n;++i){
        scanf("%d",w+i);
    }
    len = n;
    int ans = solve(k);
    cout<<ans<<endl;
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值