题解
这是一道字符串好题!
有几个重要的性质帮助解题:
1. 每次只增加一个字符,答案不会变差
2. 如果以i开头最优答案为k,则1,…,k - 1,都行
3. 用dp[i]表示i开头的最优答案,dp[i + 1] + 1 >= dp[i]
所以每次只需要枚举答案k,check当前是否合法。
注意check的时候是询问是否有位置j满足
1. j >= i + k , lcp(i,j) 或 lcp(i + 1,j) >= k - 1
2. f[j] >= k - 1 这里的>=很重要!我们可能舍掉一些字符来发现更优解。一开始想直接查询子串的所有出现位置来更新答案,这样是错的!
我们注意到lcp(i,j) >= k - 1的后缀在后缀数组中构成区间,可以二分找到左右端点。
而从后往前dp的时候,i + k是不增的,所以直接把合法的位置加入线段树即可。(这里不用删除,非常好写)
总结:
终于会应用SA的模板了。SA和SAM各有用处,都必须能够熟练应用!
这道题的代码主要是用模板构成,在ACM中一定要会熟练应用模板。模板的内容确定是正确的,减少很多不必要的调试时间!
这道题最开始想法有问题,没有make sure。直到一个月后在复习和实现的时候才发现。所以复习和把代码实现一遍非常重要。并且每次想题都要从头开始,确认每一个细节!
#include<bits/stdc++.h>
using namespace std;
#define rep(i,l,r) for(register int i = l ; i <= r ; i++)
#define repd(i,r,l) for(register int i = r ; i >= l ; i--)
#define rvc(i,S) for(register int i = 0 ; i < (int)S.size() ; i++)
#define rvcd(i,S) for(register int i = ((int)S.size()) - 1 ; i >= 0 ; i--)
#define fore(i,x)for (register int i = head[x] ; i ; i = e[i].next)
#define forup(i,l,r) for (register int i = l ; i <= r ; i += lowbit(i))
#define fordown(i,id) for (register int i = id ; i ; i -= lowbit(i))
#define pb push_back
#define prev prev_
#define stack stack_
#define mp make_pair
#define fi first
#define se second
#define lowbit(x) (x&(-x))
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<int,int> pr;
const ll inf = 2e18;
const int N = 3e6 + 10;
const int maxn = 500020;
const ll mod = 998244353;
int f[maxn],p;
char ch[maxn];
//================================SA====================================
int s[maxn],c[maxn],t1[maxn * 2],t2[maxn * 2],sa[maxn],rk[maxn],h[maxn];
int n,a[maxn];
int mn[20][maxn * 2],cnt[maxn];
//sa[i]表示排名为i的后缀的位置,下标从0开始
//rk[i]表示第i个后缀的排名,下标从0开始
//x[i]表示i的排名(第一关键字)
//y[i]表示第二关键字排名为i的后缀的起始位置
//h[i]表示后缀i在排序后与前一位的lcp
//求i,j的lcp是排序后rk[i],rk[j],height的min
//接口,n为串长。字符串读入为s[i],下标从0开始即可
void suffix_array(){
int m = 200 , *x = t1 , *y = t2; //m是值域,如果是字符开200,如果值域是n则开成n
rep(i,0,m) c[i] = 0;
rep(i,0,n - 1) c[x[i] = s[i]]++;
rep(i,1,m) c[i] += c[i - 1];
rep(i,0,n - 1) sa[--c[x[i]]] = i;
// rep(i,1,n) cout<<sa[i - 1]<<" ";
// cout<<endl;;
for (register int k = 1 ; k < n ; k <<= 1){
register int p = 0;
memset(y,0,sizeof(t1));
repd(i,n - 1,n - k) y[p++] = i; //必须倒着for,在后面的位置更小
rep(i,0,n - 1) if ( sa[i] >= k ) y[p++] = sa[i] - k;
rep(i,0,m) c[i] = 0;
rep(i,0,n - 1) c[x[y[i]]]++;
rep(i,1,m) c[i] += c[i - 1];
repd(i,n - 1,0) sa[--c[x[y[i]]]] = y[i];
p = 0 , swap(x,y) , x[sa[0]] = ++p;
rep(i,1,n - 1) x[sa[i]] = (y[sa[i]] == y[sa[i - 1]]) && (y[sa[i] + k] == y[sa[i - 1] + k]) ? p : ++p;
if ( p >= n ) break; //如果当前已经完成排名,则break
m = p;
}
rep(i,0,n - 1) rk[sa[i]] = i;
int k = 0;
rep(i,0,n - 1){
if ( !rk[i] ) continue;
int j = sa[rk[i] - 1];
if ( k ) k--;
while ( s[j + k] == s[i + k] ) k++;
h[rk[i]] = k;
}
/* rep(i,0,n - 1) cout<<sa[i]<<" "<<h[i]<<endl;;
cout<<endl;;
rep(i,0,n - 1) cout<<rk[i]<<" ";
cout<<endl;*/
}
//求两个后缀的最长公共前缀
//在后缀排序后的h数组中用区间RMQ查最小值
namespace Seg{
#define ls(x) (x << 1)
#define rs(x) ((x << 1) | 1)
const int M = 5e5 + 20;
int mx[M << 2];
inline void update(int x){
mx[x] = max(mx[ls(x)],mx[rs(x)]);
}
void modify(int x,int l,int r,int id,int d){
if ( l == r ){
mx[x] = d;
return;
}
int mid = (l + r) >> 1;
if ( id <= mid ) modify(ls(x),l,mid,id,d);
else modify(rs(x),mid + 1,r,id,d);
update(x);
}
int querymx(int x,int l,int r,int L,int R){
if ( L > R ) return 0;
if ( L <= l && R >= r ) return mx[x];
// pushdown(x);
int mid = (l + r) >> 1; int res = 0;//从0开始,为了方便,把初始设为0
if ( L <= mid ) res = querymx(ls(x),l,mid,L,R);
if ( R > mid ) res = max(res,querymx(rs(x),mid + 1,r,L,R));
return res;
}
}
using namespace Seg;
void init(){
int k = 0;
rep(i,0,n){
if ( i > (1 << (k + 1)) ) k++;
cnt[i] = k;
}
suffix_array();
rep(i,0,n - 1) mn[0][i] = h[i];
rep(i,1,19)
rep(j,0,n - 1)
mn[i][j] = min(mn[i - 1][j],mn[i - 1][j + (1 << (i - 1))]);
}
inline int lcp(int x,int y){ //x,y是相应的排名
if ( y == -1 || y >= n ) return 0;
if ( x == y ) return n;
if ( x > y ) swap(x,y);
x++;
int c = cnt[y - x + 1];
return min(mn[c][x],mn[c][y - (1 << c) + 1]);
}
inline int findl(int id,int len){
int l = 0 , r = id , res = id;
while ( l <= r ){
int mid = (l + r) >> 1;
if ( lcp(id,mid) >= len ) res = mid , r = mid - 1;
else l = mid + 1;
}
return res;
}
inline int findr(int id,int len){
int l = id , r = n - 1 , res = id;
while ( l <= r ){
int mid = (l + r) >> 1;
if ( lcp(id,mid) >= len ) res = mid , l = mid + 1;
else r = mid - 1;
}
return res;
}
pr getid(int id,int len){ //查询和id开始的后缀lcp>=len的排名区间
return mp(findl(rk[id],len),findr(rk[id],len));
}
bool check(int id,int len){
while ( p >= id + len ) modify(1,0,n - 1,rk[p],f[p]) , p--;
pr cur = getid(id,len - 1);
if ( querymx(1,0,n - 1,cur.fi,cur.se) >= len - 1 ) return 1;
cur = getid(id + 1,len - 1);
if ( querymx(1,0,n - 1,cur.fi,cur.se) >= len - 1 ) return 1;
return 0;
}
int main(){
scanf("%d %s",&n,ch);
rep(i,0,n - 1) s[i] = ch[i];
init();
if ( n <= 2 ) return puts("1"),0;
int ans = 0;
p = n - 1 , f[n - 1] = f[n - 2] = 1;
repd(i,n - 3,0){
f[i] = 1;
repd(k,f[i + 1] + 1,1){
if ( check(i,k) ){ f[i] = k; break; }
}
ans = max(ans,f[i]);
}
printf("%d\n",ans);
}