洛谷 P3082 思路分享(kmp,dp)

https://www.luogu.com.cn/problem/P3082

题意概述

给定两字符串 \(s,t\),长度分别为 \(n,m\).

通过移除 \(s\) 中若干字符,让 \(s\) 的任意一个子串中都不包含 \(t\),求最小移除的字符数.

\(1\le n \le 10^4\)\(1\le m \le 10^3\).

思路

将求最小移除字符数转化成求最大能保留的长度.

考虑 \(dp\),令 \(dp[i][j]\) 为处理到 \(s\)\(i\) 个字符,保留的字符串后缀匹配了 \(t\) 的前 \(j\) 个字符,能保留的最大长度.

转移考虑两种情况:

  • 移除 \(s[i+1]\)\(dp[i+1][j] = \max(dp[i+1][j],dp[i][j])\).

  • 保留 \(s[i+1]\),此时要得到加上 \(s[i+1]\) 后与 \(t\) 的匹配情况,这和 \(kmp\) 的逻辑一样,如果失配就根据 \(next\) 往前跳,可以做一下类似路径压缩的预处理. 定义 \(f[j][c]\) 为当前匹配了 \(t\) 的前 \(j\) 个字符,下一个字符为 \(c\),加上 \(c\) 后匹配的情况,那么转移方程为:

\[dp[i+1][f[j][s[i]-'a']] = \max(dp[i+1][f[j][s[i]-'a']],dp[i][j]+1) \]

这里字符串是 \(0-based\).

\(f\) 可以递推求解:

\[f[i][c] = \begin{cases} i+1,t[i]-'a' = c \\[1em] f[next[i]][c],otherwise \end{cases} \]

最终答案为 \(n-\max(dp[n][0],\cdots,dp[n][m-1])\).

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

代码

//author:kzssCCC

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

const int INF = 1e9;

void solve(){
	string s,t;
	cin >> s >> t;

	int n = s.size();	
	int m = t.size();

	vector<int> next(m+1);
	int cmp=0,cur=2;
	while (cur<=m){
		if (t[cmp]==t[cur-1]){
			next[cur] = cmp+1;
			cmp++,cur++;
		}
		else if (cmp){
			cmp = next[cmp];
		}
		else{
			cur++;
		}
	}	

	vector<vector<int>> f(m,vector<int>(26));
	for (int i=0;i<m;i++){
		for (int c=0;c<26;c++){
			if (t[i]-'a'==c){
				f[i][c] = i+1;
			}
			else{
				f[i][c] = f[next[i]][c];
			}
		}
	}

	vector<int> dp(m+1,-INF);
	dp[0] = 0;

	for (int i=0;i<n;i++){
		auto ndp = dp;
		for (int j=0;j<m;j++){
			if (dp[j]==-INF) continue;

			int k = f[j][s[i]-'a'];
			ndp[k] = max(ndp[k],dp[j]+1);
		}

		dp = ndp;
	}

	int mx = -INF;
	for (int i=0;i<m;i++){
		mx = max(mx,dp[i]);	
	}
	cout << n-mx << '\n';
}

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

	return 0;
}
posted @ 2026-06-14 13:29  kzssCCC  阅读(12)  评论(0)    收藏  举报