【题解】Codeforces1063F. String Journey 后缀数组+DP+线段树优化

对于一组字符串,如果后一个是前一个的真子串,就把它们称作是 t o u r i s t tourist ,它的 r a t i n g rating 定义为字符串的数量。

给定长为 n ( 5 e 5 ) n(5e5) 的字符串 s s ,在其中找到一个 t o u r i s t tourist ,满足所有字符串都是 s s 的子串,前后两个字符串的位置不相交,且在 s s 中依次出现,求最大的 r a t i n g rating

时间限制5s, 空间限制512M


简单分析

答案最多只有1000,且答案满足二分性质.

有一个贪心的结论, t o u r i s t tourist 里面的字符串长度一定是 k , k 1 , k 2 , k 3... k,k-1,k-2,k-3... 这样。

动态规划

d p [ i ] dp[i] 表示最左边的子串从 i i 位置开始时的答案,有以下性质:

  1. 这个子串一定长为 d p [ i ] dp[i] ,也就是说子串是 s [ i , i + d p [ i ] 1 ] s[i,i+dp[i]-1]
  2. d p [ i ] dp[i] 满足二分性质,即 d p [ i ] 1 , d p [ i ] 2 , d p [ i ] 3 dp[i]-1,dp[i]-2,dp[i]-3 等也可以找到对应的方案,可以朝左侧转移,而 d p [ i ] + 1 dp[i]+1 不行。
  3. d p [ i ] d p [ i + 1 ] + 1 dp[i]\le dp[i+1]+1 ,因为对于 d p [ i ] dp[i] 的方案,如果把第一个字符全部扔掉,就可以构造出一个方案使得 d p [ i + 1 ] = d p [ i ] 1 dp[i+1]=dp[i]-1 ,所以 d p [ i + 1 ] dp[i+1] 一定不会小于 d p [ i ] 1 dp[i]-1 .

边界: d p [ n ] = 1 dp[n]=1

转移:由上述性质2和性质3,在求 d p [ i ] dp[i] 时,可以从 d p [ i + 1 ] + 1 dp[i+1]+1 开始向下枚举 d p [ i ] dp[i] 的值并 c h e c k check ,直到首次合法为止。

可以证明,时间复杂度为 O ( n c h e c k ) O(n*单次check耗时) .(证明方法同理于这篇文章的优化一)

扫描二维码关注公众号,回复: 9221390 查看本文章

check(i,k)

现在的目标是判断 d p [ i ] dp[i] 的值能否等于 k k .

如果合法,这里的子串是 s [ i , i + k 1 ] s[i,i+k-1] ,且它的后一个子串一定和 s [ i , i + k 2 ] s[i,i+k-2] s [ i + 1 , i + k 1 ] s[i+1,i+k-1] 完全相同。

详细地,必须存在一个位置 j j ,满足:

  1. d p [ j ] k 1 dp[j]\ge k-1
  2. j i + k j\ge i+k
  3. l c p ( j , i ) k 1 lcp(j,i)\ge k-1 或者 l c p ( j , i + 1 ) k 1 lcp(j,i+1)\ge k-1

后缀数组

将问题映射到后缀数组上,第三个条件是要求目标位置和给定位置的lcp在一定范围内,可以二分 m i n _ h i g h t min\_hight 得到一个子数组,然后在子数组中解决问题。

现在问题又变成了,给定两个数组 d p s a dpsa s a sa ,若干次查询:

每次选定一个区间,问区间中是否存在一个位置 j j ,满足 d p s a [ j ] k 1 dpsa[j]\ge k-1 s a [ j ] i + k sa[j]\ge i+k

这是一个数据结构问题,而且需要满足带修改+强制在线,据说能用主席树做,我没想到。

最后的优化

回忆一下, c h e c k ( i , k ) check(i,k) 的输入值 ( i , k ) (i,k) 相比前一次 c h e c k check 的变化,只会有两种:

  1. k k 减少了 1 1 i i 不变,表示上一次 c h e c k check 失败了。
  2. k k 增加了 1 1 i i 减少了 1 1 ,表示 c h e c k check 成功,开始求更左边一个位置的 d p dp 值.

对于 i + k i+k 来说,要么不变,要么减少 1 1

所以我们可以把所有 s a [ j ] < i + k sa[j]<i+k 的位置的 d p dp 值都存下来,然后 d p dp 值记作 0 0 ,这样处理的时候就可以当作它们不存在。等到 s a [ j ] sa[j] 合法了,再将存起来的值实装。

此时,问题就变成了单点修改+区间求max.

使用线段树处理即可.

复杂度分析

预处理:后缀数组+ST表,共 O ( n log n ) O(n\log n)

DP:状态数 O ( n ) O(n) ,转移一共需要 O ( n ) O(n) c h e c k check

check:二分子数组 O ( log n ) O(\log n) ,线段树操作 O ( log n ) O(\log n)

总时间复杂度: O ( n log n ) O(n\log n)

实现细节

  1. check:
    是否有一个位置j,满足dp[j]>=k-1,j>=i+k,max(lcp(j,i),lcp(j,i+1))>=k-1
    在后缀数组上:
    是否有一个位置j,满足某个区间内,dpsa[j]>=k-1, sa[j]>=i+k
    让所有sa[j]<i+k的dpsa[j]先自闭.
  2. 二分
    二分height得到区间,然后求区间最大值是否大于等于k-1
    lcp(j,i)>k-1的区间,是什么呢?
    设左端点lef=rk[i], 如果height[lef]>=k-1,那么lef–
    设右端点rig=rk[i], 如果height[rig+1]>=k-1, 那rig++
    所以我需要找到mih(rk[i]+1,rig)>=k-1的最后一个rig
    还有mih(lef+1,rk[i])>=k-1的第一个lef
  3. 线段树
    线段树维护的值是dpsa[],这个时候sa[j]==i+k,那么j=rk[i+k]。

各种带等于的边界纠结了不少时间。

ans初始化为0导致wa一次。

/* LittleFall : Hello! */
#include <bits/stdc++.h>
using namespace std; using ll = long long; inline int read();
const int M = 500016, MOD = 1000000007;

namespace SA
{
    void st_init(int *arr, int n);
    
    /* 后缀数组 */
    int sa[M], rk[M], height[M]; //后缀三数组,sa和rk下标从0开始,height下标从1开始
    int t1[M], t2[M], c[M]; // 用于基数排序的三个辅助数组
    void build(char *str, int n, int m) // 构造后缀三数组,字符串下标从0开始,n表示长度,m表示字符集大小
    {  
        str[n] = 0;
        n++;  
        int i, j, p, *x = t1, *y = t2;  
        for(i = 0; i < m; i++) c[i] = 0;  
        for(i = 0; i < n; i++) c[x[i]=str[i]]++;  
        for(i = 1; i < m; i++) c[i] += c[i-1];  
        for(i = n-1; i >= 0; i--) sa[--c[x[i]]] = i;  
        for(j = 1; j <= n; j<<=1)  
        {  
            p = 0;  
            for(i = n-j; i < n; i++) y[p++] = i;  
            for(i = 0; i < n; i++) if(sa[i] >= j) y[p++] = sa[i]-j;  
            for(i = 0; i < m; i++) c[i] = 0;  
            for(i = 0; i < n; i++) c[x[y[i]]]++;  
            for(i = 1; i < m; i++) c[i] += c[i-1];  
            for(i = n-1; i >= 0; i--) sa[--c[x[y[i]]]] = y[i];  
            swap(x, y);  
            p = 1; x[sa[0]] = 0;  
            for(i = 1; i < n; i++)  
                x[sa[i]] = (y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+j]==y[sa[i]+j]) ? p-1 : p++;  
            if(p >= n) break;  
            m = p;  
        }  
        n--;  
        for(int i = 0; i <= n; i++) rk[sa[i]] = i;  
        for(int i=0, j=0, k=0; i < n; i++)  
        {   
            if(k) k--;  
            j = sa[rk[i]-1];  
            while(str[i+k]==str[j+k]) k++;  
            height[rk[i]] = k;  
        }  
        st_init(height, n);
    }

    /* ST表 */
    int lg[M], _n;
    int table[20][M];
    void st_init(int *arr, int n)
    {
        _n = n;
        if(!lg[0])
        {
            lg[0]=-1;
            for(int i=1;i<M;i++)
                lg[i]=lg[i/2]+1;
        }
        for(int i=1; i<=n; ++i)
            table[0][i] = arr[i];
        for(int i=1; i<=lg[n]; ++i)
            for(int j=1; j<=n; ++j)
                if(j+(1<<i)-1 <= n)
                    table[i][j] = min(table[i-1][j], table[i-1][j+(1<<(i-1))]);
    }
    //height最小值
    int mih(int l, int r)
    {
        int t = lg[r-l+1];
        return min(table[t][l], table[t][r-(1<<t)+1]);
    }
};
using SA::sa; using SA::mih;
using SA::rk;
struct SGT
{
	int save[M<<2], m;
	void build(int n) //含read
	{
		for(m=1; m<=n+1; m<<=1) 
			continue;
	}
	int query(int s, int t) //sum[s,t]
	{
		int ans = 0;
		for(s+=m-1, t+=m+1; s^t^1; s>>=1, t>>=1)
		{
			if(~s&1) ans=max(ans, save[s^1]);
			if( t&1) ans=max(ans, save[t^1]);
		}
		return ans;
	}
	void modify(int n, int val) //save[n]=val
	{
		for(save[n+=m]=val, n>>=1; n; n>>=1)
			save[n] = max(save[n<<1], save[n<<1|1]);
	}
}sgt; //线段树维护dpsa
int n;
char str[M];
int dp[M]; //dp[i]表示从i开始的答案
struct Checker
{
	pair<int,int> binary_search(int i, int k)
	{
		int p1, p2;
		{ //获得lef
			int l=1, r=rk[i], ans=rk[i];
			while(l<=r)
			{
				int m = (l+r)>>1;
				if(mih(m+1, rk[i])>=k-1)
					ans = m, r = m-1;
				else l = m+1;
			}
			p1 = ans;
		}
		{
			int l=rk[i]+1, r=n, ans=rk[i];
			while(l<=r)
			{
				int m = (l+r)>>1;
				if(mih(rk[i]+1, m)>=k-1)
					ans = m, l = m+1;
				else r = m-1;
			}
			p2 = ans;
		}
		return {p1,p2};
	}
	bool check(int i, int k)
	{
		sgt.modify(rk[i+k], dp[i+k]); //激活下标等于i+k的dp,

		auto p1 = binary_search(i,k);
		if(sgt.query(p1.first, p1.second)>=k-1) return 1;
		auto p2 = binary_search(i+1,k);
		if(sgt.query(p2.first, p2.second)>=k-1) return 1;
		return 0;
	}
}checker;

int main(void)
{
	#ifdef _LITTLEFALL_
	freopen("in.txt","r",stdin);
    #endif

	n = read(); 
	sgt.build(n);
	int ans = 1;
	scanf("%s", str);
	SA::build(str, n, 128);
	dp[n-1] = 1;
	for(int i=n-2; i>=0; --i)
	{
		for(int j=dp[i+1]+1; j>=0; --j)
			if(checker.check(i,j)) dp[i]=j, j=-1;
		ans = max(ans, dp[i]);
	}
	printf("%d\n",ans );

    return 0;
}


inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
发布了375 篇原创文章 · 获赞 305 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/m0_37809890/article/details/102981571
今日推荐