Codeforces Round 943 (Div. 3) G2

题意

f ( i ) f(i) f(i) 表示将字符串 s 分为 i 段,每一段的最长公共前缀的最长长度。

f ( l ) , f ( l + 1 ) . . . f ( r ) f(l),f(l+1)...f(r) f(l),f(l+1)...f(r)

解题思路

假如 s s s 某个前缀 t t t s s s 中最多可以找到 x x x 个不相交和 t t t 相同的子串,比如ababababab,对于前缀abab,最多可以找到 2 个不相交的和 abab相同的子串。

所以分成小于等于 x x x 段的结果都可以使用 t t t 的长度去更新,所以我们只需要枚举字符串的所有前缀。

考虑对原串构建后缀自动机,在后缀树上用启发式合并维护 endpos 集合,每 dfs 到表示前缀的节点,我们按照上方思路贪心的找到最多的不相交的子串(位置靠前优先选)每个 endpos 集合最大为 n,每次最多跳 ⌊ n l e n ( p r e f i x ) ⌋ \left \lfloor \frac{n}{len(prefix)} \right \rfloor len(prefix)n 次,总共最多 ∑ i = 1 n ⌊ n i ⌋ \sum_{i=1}^{n}\left \lfloor \frac{n}{i} \right \rfloor i=1nin 次,每次跳使用lower_bound,时间复杂度 l o g ( n ) log(n) log(n),复杂度约等于 O ( n × l n ( n ) × l o g ( n ) ) O(n\times ln(n)\times log(n)) O(n×ln(n)×log(n)),启发式合并复杂度 O ( n × l o g 2 ( n ) ) O(n\times log^{2}(n)) O(n×log2(n))

总时间复杂度 O ( n × l o g 2 ( n ) ) O(n\times log^2(n)) O(n×log2(n))

AC_Code

//
// Created by liuhao.
//
#include<bits/stdc++.h>
using namespace std;
#define ios ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define endl '\n'

template<class T>
struct Suffix_Automaton {
	int tot = 1, last = 1;
	struct Node {
		signed len, fa, fipos;
		int ch[26];
	};
	vector<Node> node;
	int n;
	Suffix_Automaton(T &s) : n(s.size() - 1), node(2 * s.size()) {
		for (int i = 1; i < s.size(); i++)extend(s[i]-'a');
	}
	void extend(int c) {
		int p = last, np = last = ++tot;
		node[np].len = node[p].len + 1;
		node[np].fipos = node[np].len;
		for (; p && !node[p].ch[c]; p = node[p].fa)node[p].ch[c] = np;
		if (!p)node[np].fa = 1;
		else {
			int q = node[p].ch[c];
			if (node[q].len == node[p].len + 1)node[np].fa = q;
			else {
				int cp = ++tot;
				node[cp] = node[q], node[cp].len = node[p].len + 1;
				node[cp].fipos=0;
				node[q].fa = node[np].fa = cp;
				for (; p && node[p].ch[c] == q; p = node[p].fa)node[p].ch[c] = cp;
			}
		}
	}
	int size() { return tot; }
	Node operator[](int i) {
		return node[i];
	}
};

auto main() -> signed {
	ios;
	int T;
	cin>>T;
	while(T--){
	    int n,l,r;
		cin>>n>>l>>r;
		string s;
		cin>>s;
		s=' '+s;
		Suffix_Automaton<string>suf(s);
		vector<vector<int>>g(suf.size()+1);
		for(int i=2;i<=suf.size();i++){
			g[suf[i].fa].push_back(i);
		}
		vector<int>d(n+1);
		vector<set<int>>S(suf.size()+1);
		auto dfs=[&](auto dfs,int x)->void{
			for(auto i:g[x]){
				dfs(dfs,i);
				if(S[i].size()>S[x].size())S[i].swap(S[x]);
				for(auto j:S[i]){
					S[x].insert(j);
				}
			}
			if(suf[x].fipos!=0){
				S[x].insert(suf[x].fipos);
				int last=0;
				int res=0;
				for(auto i=S[x].begin();i!=S[x].end();){
					auto t=S[x].lower_bound(last+suf[x].len);
					if(t==S[x].end())break;
					last=*t;
					res++;
				}
				d[res]=max(suf[x].len,d[res]);
			}
		};
		dfs(dfs,1);
		for(int i=n-1;i>=1;i--){
			d[i]=max(d[i+1],d[i]);
		}
		for(int i=l;i<=r;i++){
			cout<<d[i]<<' ';
		}
		cout<<endl;
	}
    return 0;
}

  • 34
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值