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;
}

浙公网安备 33010602011771号