【题解】Codeforces 1063F. String Journey 后缀数组+贪心

20 篇文章 0 订阅
18 篇文章 0 订阅

题目

题解

这是一道字符串好题!
有几个重要的性质帮助解题:
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);
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值