POJ1743 Musical Theme 后缀数组

本文介绍了一种解决音乐主题中寻找最长重复子串的方法,通过差分处理和二分查找来确定最长公共前缀的长度,实现了高效的算法实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Musical Theme

典型的不可重叠最长重复子串问题。

输入处理

  • 对输入做差分
  • 用后一项减去前一项
    • 1 2 4 7 -> 1 2 3
  • 变调也认为是一样,即1 2 4 7 和 3 4 6 9是一样的
  • 差分后长度减一

二分

二分一个k值,k表示最长公共前缀的长度

  • check(k)为真,则说明k可能可以更大。check(k)为假,说明k应该更小。

check验证函数

k是否让一个验证函数check为真

  • 遍历h数组。
  • 利用k把h数组划分为若干组,每组之间的h数组都要大于等于k
    • 该组内任意两个前缀的最长公共前缀都大于等于k。
  • 记录一个组内后缀下标的最大值最小值
    • 最大值-最小值>=k 表示组内有两个串有不相交的长为k的公共前缀。
    • 若以上条件满足,返回真。

代码

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

int sa[N + 1], ranks[N + 1], height[N + 1];
int sz; // 后缀数组长度
int cnt[N + 1]; //基数排序的桶
int input[N + 1]; //原始数组

struct node {
    int v[2]; // v[0],v[1]表示二元对。v[0]是高位,v[1]是低位。
    int p; // p表示这个后缀的起始位置。
    bool operator == (const node & n) const {
        return v[0] == n.v[0] && v[1] == n.v[1];
    }
    bool operator < (const node &n) const {
        if (v[0] != n.v[0]) return v[0] < n.v[0];
        return v[1] < n.v[1];
    }
}arr[N + 1], temp[N + 1];

void print() {
    cout << "sa" << endl;
    for(int i = 1; i <= sz; i++) cout << sa[i] << " ";
    cout << endl << "rank" << endl;
    for(int i = 1; i <= sz; i++) cout << ranks[i] << " ";
    cout << endl << "height" << endl;
    for(int i = 1; i <= sz; i++) cout << height[i] << " ";
    cout << endl;
}

// 填入ranks数组。注意相同的后缀的排名应该相同
void ra() {
    for(int i = 1, j = 1, k = 1; i <= sz; i = j, k++) {
        while(j <= sz && arr[i] == arr[j]) {
            ranks[arr[j++].p] = k;
        }
    }
}

// 基数排序 桶的范围是[0, base]
void sort(int base) {
    for(int i = 1; i >= 0; i--) {
        memset(cnt, 0, sizeof(int) * (base + 1));
        for(int j = 1; j <= sz; j++) cnt[arr[j].v[i]]++;
        for(int j = 1; j <= base; j++) cnt[j] += cnt[j - 1];
        for(int j = sz; j > 0; j--) temp[cnt[arr[j].v[i]]--] = arr[j]; // 从后往前遍历,这样是稳定的
        memcpy(arr, temp, sizeof(node) * (sz + 1));
    }
    ra();
}

void buildSA() {
    for(int i = 1; i <= sz; i++) {
        arr[i].p = i;
        arr[i].v[0] = input[i];
        arr[i].v[1] = 0;
    }
    sort(arr + 1, arr + sz + 1); //第一次有负数直接排序,不影响最终复杂度
    ra();
    for(int i = 1; i < sz; i <<= 1) {
        for(int j = 1; j <= sz; j++) {
            arr[j].v[0] = ranks[j];
            arr[j].v[1] = j + i <= sz ? ranks[j + i] : 0;
            arr[j].p = j;
        }
        sort(sz);
    }
    for(int i = 1; i <= sz; i++) {
        sa[ranks[i]] = i;
    }
}

void lcp() {
    int len = 0;
    for(int i = 1; i <= sz; i++) {
        if (len > 0) len--;
        if (ranks[i] > 1) {
            while(input[i + len] == input[sa[ranks[i] - 1] + len]) len++;
        }
        height[ranks[i]] = len;
    }
}

bool check(int k) {
    int mx = sa[1], mn = sa[1];
    for(int i = 2; i <= sz; i++) {
        if(height[i] >= k) {
            mx = max(mx, sa[i]);
            mn = min(mn, sa[i]);
        } else {
            if(mx - mn >= k) return true;
            mx = mn = sa[i];
        }
    }
    return mx - mn >= k;
}

int solve() {
    int lb = 0, rb = sz + 1;
    while(lb + 1 < rb) {
        int mid = (lb + rb) >> 1;
        if (check(mid)) lb = mid;
        else rb = mid;
    }
    return lb;
}

int main(int argc, const char * argv[]) {
    while(true) {
        scanf("%d", &sz);
        if (sz == 0) break;
        int last = 0;
        for(int i = 1; i <= sz; i++) {
            scanf("%d", &input[i]);
            if (i > 1) input[i - 1] = input[i] - last;
            last = input[i];
        }
        sz--;
        if (sz < 5) {
            cout << 0 << endl;
            continue;
        }
        buildSA();
        lcp();
        //print();
        int res = solve();
        res = res >= 4 ? res + 1 : 0;
        cout << res << endl;
    }
    return 0;
}

参考

http://www.cnblogs.com/zcwwzdjn/archive/2012/03/09/2388413.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值