• String Journey


    这里写一篇后缀数组的题解。

    首先我们考虑一个贪心,我们不断划分,则最后一个区间的最优情况一定是只有一个元素的,同理那么我们的第(i)个区间的字符个数一定比第(i+1)个区间的字符个数多(1)

    我们先考虑一个(Theta(n^2))(dp),定义(dp_i)表示将([i,i+dp_i-1])划分一个区间,则我们可以枚举一个(j),满足(j>i)

    (k = min(j - i , dp_j+1))同时( exttt{hash}(i , i+k-2) = exttt{hash}(j , j + k-2))( exttt{hash}(i+1 , i+k-1)= exttt{hash}(j , j + k-2))
    (dp_i=max(dp_i,k))

    我们成功实现了(n^2)的做法。

    现在我们考虑打表找规律,发现一个有用的性质:

    (dp_i leq dp_{i+1}+1)

    因此我们可以考虑记录一个(cur),表示我们在对(i)转移时,(i)所能够达到的最大的位置。现在考虑如果(i)能够达到这个值需要满足哪些条件,即存在一个(j > cur),同时(cur-i leq dp_j),同时( exttt{hash}(i , cur-1)= exttt{hash}(j,j+cur-i-1))或者( exttt{hash}(i+1 , cur)= exttt{hash}(j,j+cur-i-1))。如果无法达到,那么我们可以退而求其次,(cur-1),进行下一轮判断。

    我们考虑求一个后缀数组,那么我们就可以用二分在(height)数组上找到一个区间,使得这个区间的每一个元素与(i)(LCP)都大于等于(cur-i),现在我们想要知道这个区间内是否存在一个元素(j>cur)(dp)值大于等于(cur-i),所以我们可以用线段树维护一下最大值,每次(cur-1)时,把(dp)值加入即可。

    下面是代码:

    #include <cmath>
    #include <cstdio>
    #include <algorithm>
    using namespace std;
    const int MAXN = 5e5 + 5;
    int sa[MAXN] , rk[MAXN] , x[MAXN] , y[MAXN] , t[MAXN] , cnt[MAXN] , n , height[MAXN];
    char s[MAXN];
    void make_suffix() {
    	int m = 256;
    	for (int i = 1; i <= n; ++i) cnt[x[i] = s[i]] ++;
    	for (int i = 1; i <= m; ++i) cnt[i] += cnt[i - 1];
    	for (int i = 1; i <= n; ++i) sa[cnt[x[i]] --] = i;
    	for (int k = 1; k <= n; k <<= 1) {
    		int tot = 0;
    		for (int i = n - k + 1; i <= n; ++i) y[++tot] = i;
    		for (int i = 1; i <= n; ++i) if(sa[i] > k) y[++tot] = sa[i] - k;
    		for (int i = 1; i <= m; ++i) cnt[i] = 0;
    		for (int i = 1; i <= n; ++i) cnt[x[y[i]]] ++;
    		for (int i = 1; i <= m; ++i) cnt[i] += cnt[i - 1];
    		for (int i = n; i >= 1; --i) sa[cnt[x[y[i]]] --] = y[i];
    		tot = 1;
    		t[sa[1]] = 1;
    		for (int i = 2; i <= n; ++i) {
    			if(x[sa[i]] == x[sa[i - 1]] && x[sa[i] + k] == x[sa[i - 1] + k]) t[sa[i]] = tot;
    			else t[sa[i]] = ++tot; 
    		}
    		if(tot >= n) break;
    		for (int i = 1; i <= n; ++i) x[i] = t[i];
    		m = tot + 1;
    	}
    	for (int i = 1; i <= n; ++i) rk[sa[i]] = i;
    }
    void make_height() {
    	int k = 0;
    	for (int i = 1; i <= n; ++i) {
    		if(k) k --;
    		int j = sa[rk[i] - 1];
    		while(s[i + k] == s[j + k]) k ++;
    		height[rk[i]] = k;
    	}
    }
    int st[MAXN][22] , dp[MAXN] , tr[MAXN << 2];
    void update(int l , int r , int now , int x , int y) {
    	if(l == r) {
    		tr[now] = y;
    		return;
    	}
    	int mid = (l + r) >> 1;
    	if(x <= mid) update(l , mid , now << 1 , x , y);
    	else update(mid + 1 , r , now << 1 | 1 , x , y);
    	tr[now] = max(tr[now << 1] , tr[now << 1 | 1]);
    }
    int get_min(int l , int r) {
    	if(l > r) return n;
    	int x = log(r - l + 1) / log(2);
    	return min(st[l][x] , st[r - (1 << x) + 1][x]);
    }
    int get_l(int x , int y) {
    	int l = 1 , r = x , ans = 0;
    	while(l <= r) {
    		int mid = (l + r) >> 1;
    		if(get_min(mid + 1 , x) >= y) r = mid - 1 , ans = mid;
    		else l = mid + 1;
    	}
    	return ans;
    } 
    int get_r(int x , int y) {
    	int l = x , r = n , ans = 0;
    	while(l <= r) {
    		int mid = (l + r) >> 1;
    		if(get_min(x + 1 , mid) >= y) l = mid + 1 , ans = mid;
    		else r = mid - 1;
    	}
    	return ans;
    }
    int find(int l , int r , int now , int x , int y) {
    	if(l >= x && r <= y) {
    		return tr[now];
    	}
    	int mid = (l + r) >> 1 , res = 0;
    	if(x <= mid) res = max(res , find(l , mid , now << 1 , x , y));
    	if(y > mid) res = max(res , find(mid + 1 , r , now << 1 | 1 , x , y));
    	return res;
    }
    bool check(int x , int y) {
    	int l = get_l(x , y) , r = get_r(x , y);
    	if(find(1 , n , 1 , l , r) >= y) return false;
    	return true;
    }
    int main() {
    	scanf("%d" , &n);
    	if(n == 1) {
    		printf("1
    ");
    		return 0;
    	}
    	scanf("%s" , s + 1);
    	make_suffix();make_height();
    	for (int i = 1; i <= n; ++i) st[i][0] = height[i];
    	for (int j = 1; (1 << j) <= n; ++j) {
    		for (int i = 1; i + (1 << j) - 1 <= n; ++i) {
    			st[i][j] = min(st[i][j - 1] , st[i + (1 << (j - 1))][j - 1]);
    		}
    	}
    	int cur = n;dp[n] = 1;
    //	update(1 , n , 1 , rk[n] , dp[n]);
    	int ans = 0;
    	for (int i = n - 1; i >= 1; --i) {
    		while(check(rk[i] , cur - i) && check(rk[i + 1] , cur - i) && i < cur) cur -- , update(1 , n , 1 , rk[cur + 1] , dp[cur + 1]);
    		dp[i] = cur - i + 1;ans = max(ans , dp[i]);
    	}
    	printf("%d" , ans);
    	return 0;
    }
    
  • 相关阅读:
    新的开始!
    find命令之mtime
    glances服务器监控工具
    centos7最小化安装改为图形界面
    ansible笔记(3)-文件操作模块(上)
    php-fpm参数详解
    ansible笔记(2)-模块简介
    centos创建交换分区
    等保测评三级整改-身份鉴别
    vsftp安装配置
  • 原文地址:https://www.cnblogs.com/Reanap/p/13426354.html
Copyright © 2020-2023  润新知