CF2209E 思路分享(kmp,dp)

https://codeforces.com/problemset/problem/2209/E

题意概述

对于字符串 \(t\),定义 \(f(t)\) 为:将 \(t\) 分成若干连续子串,每个子串都是 \(t\) 的某个前缀,能分出的最大子串数量.

\(q\) 个询问,每个询问给定 \(l,r\),求

\[\sum_{i=l}^{r}{f(s[l\cdots i])} \]

\(1\le n \le 10^6,1\le q \le 100\).

思路

因为 \(nq\)\(10^8\),因此暴力处理每个询问即可.

考虑 \(dp\),定义 \(dp_i\)\(f(s[1\cdots i])\).

\(i\) 位置时,可以往前跳一个 \(border\) 的距离,分析可知,对于所有能跳到 \(i\) 的位置 \(j\)\(dp_j\) 是不减的,因此可以贪心地选取最小的 \(border\).

可以使用 \(kmp\) 求出每个前缀的最小 \(border\),在标准 \(kmp\) 实现上新增一个 \(mb\) 数组表示最小 \(border\).

因为 \(border\)\(border\)\(border\),在求出 \(next[i]\) 的时候尝试更新 \(mb[i]\)\(mb[next[i]]\) 即可.

时间复杂度 \(\mathcal{O}(nq)\).

代码

//author:kzssCCC

#include <bits/stdc++.h>
using namespace std;
using ll = long long;


void solve(){
	int n,q;
	cin >> n >> q;
	string s;
	cin >> s;

	while (q--){
		int l,r;
		cin >> l >> r;
		l--,r--;
		int m = r-l+1;
		string t = s.substr(l,m);
		
		vector<int> next(m+1);
		vector<int> mb(m+1);
		int cmp=0,cur=2;

		while (cur<=m){
			if (t[cmp]==t[cur-1]){
				next[cur] = cmp+1;
				mb[cur] = cmp+1;
				if (mb[next[cur]]){
					mb[cur] = mb[next[cur]];
				}
				cmp++,cur++;
			}
			else if (cmp){
				cmp = next[cmp];
			}
			else{
				cur++;
			}
		}

		vector<int> dp(m+1);
		ll res = 0;
		for (int i=1;i<=m;i++){
			dp[i] = dp[i-mb[i]]+1;
			res += dp[i];
		}

		cout << res << '\n';
	}
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	
	int t = 1;
	cin >> t;
	while (t--) solve();

	return 0;
}
posted @ 2026-06-11 16:43  kzssCCC  阅读(13)  评论(0)    收藏  举报