Description
定义字符串(A)比(B)酷 : (B)至少在(A)中出现(2)次
给你一个长度(le 2*10^5)的字符串s
求在其中选择若干子串(随便选,可重叠), 排成一列, 且任意后一个子串比前一个子串(Cool)的方案中
最长的长度是多少
Analysis
出现两次...选了一个串要先收缩然后找到合法前缀然后再收缩什么... 没有思路啊
我们反过来从答案入手, 对于任意一个最优解, 进行下图的收缩
即: 从下到上进行如下操作. 选择每组中最左的一个, 做一条切割线, 保留上一层切割线右的部分
这样, 每一层的字符串都是上一层的前缀
根据贪心, 我们一定选择由上一层选择一个最长的出现至少2次的前缀
Solution
考虑后缀树结构
一个子串的前缀为其在后缀树上的祖先
我们需要实现 : (1)判断原串的某个前缀是否在原串出现至少两次 (2) 求出最长的一个
判断的话, 我们可以考虑(left)集
因为一个串出现在什么地方是没有影响的因为本质相同
因为前缀在开头出现了一次, 再出现一次就好了
所以我们可以求出原串的最左出现位置(p), 然后求出该前缀(>p)的最左出现位置
判断长度是否合法即可
因为要求出最长的合法, 而(fa)会导致串变短
而合法与不合法有分界线, 可以二分
所以我们树上倍增 跳出 的不合法点的极长距离, 然后再往上走一步就好了
Code
#include <cstdio>
#include <cstring>
#include <cctype>
#include <cmath>
#include <algorithm>
#define rep(i,a,b) for (int i = (a); i <= (b); ++ i)
#define per(i,a,b) for (int i = (a); i >= (b); -- i)
#define For(i,a,b) for (int i = (a); i < (b); ++ i)
using namespace std;
const int M = 2e5 + 7;
const int INF = 1e9 + 7;
int n;
char s[M];
namespace Seg{
static const int N = (M << 1) * 20 * 2;
int n, rt[N], tot;
struct node{
int lc, rc;
}a[N];
inline int newnode(int x){
a[++tot] = a[x];
return tot;
}
void ins(int &x, int l, int r, int to){
x = newnode(0);
if (l == r) return;
int mid = l+r >> 1;
if (to <= mid) ins(a[x].lc, l, mid, to);
else ins(a[x].rc, mid+1, r, to);
}
int merge(int x, int y, int l, int r){
if (!x || !y) return x | y;
if (l == r) return newnode(x);
int mid = l+r >> 1;
int nw = newnode(x);
a[nw].lc = merge(a[x].lc, a[y].lc, l, mid);
a[nw].rc = merge(a[x].rc, a[y].rc, mid+1, r);
return nw;
}
int nxt(int x, int l, int r, int p){
if (!x) return INF;
if (l == r) return (p != l) ? l : INF;
int mid = l+r >> 1;
if (p > mid) return nxt(a[x].rc, mid+1, r, p);
else{
int tp = nxt(a[x].lc, l, mid, p);
return (tp != INF) ? tp : nxt(a[x].rc, mid+1, r, p);
}
}
int nxt(int x, int p) {return nxt(rt[x], 1, n, p);}
void ins(int x, int to) {ins(rt[x], 1, n, to);}
void merge(int x, int y) {rt[x] = merge(rt[x], rt[y], 1, n);}
}
namespace ST{
static const int N = M << 1;
int n, D, pre[N][20], mx[N], stp[N], lf[N];
struct vec{
int g[N], te;
struct edge{int y, nxt;}e[N];
inline void push(int x, int y) {e[++te] = (edge){y, g[x]}; g[x] = te;}
inline int& operator () (int x) {return g[x];}
inline edge& operator [] (int x) {return e[x];}
}e;
void init(int x){
if (lf[x]) Seg::ins(x, lf[x]);
int p, y;
for (p=e(x); p; p=e[p].nxt)
if ((y=e[p].y) != pre[x][0]){
init(y);
Seg::merge(x, y);
}
}
void build(){
D = (int)log2(n);
rep (j, 1, D)
rep (i, 1, n) pre[i][j] = pre[pre[i][j-1]][j-1];
init(1);
}
int getmx(int x){
int p = Seg::nxt(x, 0);
int r = p + stp[x] - 1;
int y;
per (t, D, 0) if (y = pre[x][t]) {
int l = Seg::nxt(y, p) + stp[y] - 1;
if (l > r) x = y;
}
return mx[pre[x][0]];
}
void dfs(int x){
int p, y;
mx[x] = (x == 1) ? 0 : 1 + getmx(x);
for (p=e(x); p; p=e[p].nxt)
if ((y=e[p].y) != pre[x][0]) dfs(y);
}
int solve(){
dfs(1);
int res = 0;
rep (i, 1, n) res = max(res, mx[i]);
return res;
}
inline void link(int x, int y) {e.push(x, y); pre[y][0] = x;}
}
namespace Sam{
static const int N = M << 1;
int fa[N], stp[N], ch[N][26], lf[N];
int last, tot;
inline int newnode(int l){
stp[++tot] = l;
return tot;
}
int ext(int p, int q, int c){
int nq = newnode(stp[p] + 1);
fa[nq] = fa[q], fa[q] = nq;
memcpy(ch[nq], ch[q], sizeof ch[q]);
for (; p && ch[p][c] == q; p = fa[p]) ch[p][c] = nq;
return nq;
}
int sam(int p, int c){
int np = newnode(stp[p] + 1);
for (; p && ch[p][c] == 0; p = fa[p]) ch[p][c] = np;
if (!p) fa[np] = 1;
else{
int q = ch[p][c];
fa[np] = (stp[p] + 1 == stp[q]) ? q : ext(p, q, c);
}
return np;
}
void build(){
last = tot = 1;
per (i, n, 1) lf[last = sam(last, s[i]-'a')] = i;
}
void toST(){
ST::n = Seg::n = tot;
rep (i, 1, tot) ST::stp[i] = stp[i], ST::lf[i] = lf[i];
rep (i, 2, tot) ST::link(fa[i], i);
}
}
int main(){
scanf("%d%s", &n, s+1);
Sam::build();
Sam::toST();
ST::build();
int ans = ST::solve();
printf("%d
", ans);
return 0;
}