洛谷 P3082 Necklace G 题解

前言

(打个小广告)洛谷博客

KMP 与 DP。

题目分析

简化题意

给定字符串 ST,求在 S 中最少删几个字符才能使 S 中不出现 T

思路

看到字符串匹配类型的题目,第一时间想到 KMP 算法。考虑暴力,枚举 S 中的每一个字符删或不删,后使用 KMP 算法匹配。

看一眼数据规模 ,发现 For all test cases, N <= 10000, M <= 1000. 显然刚才的暴力不能满足题目的时限要求。

考虑优化,设动态规划状态为 f i , j f_{i, j} fi,j,表示在 S 1 1 1 i i i 位中删掉一些字符后末尾最多能与 T 1 1 1 j j j 位进行配对的最少字符删减数量。

对于动态规划的转移方程,我们可以从两种情况来考虑(对于此处的状态,我们可以发现主动转移比被动转移的代码与思路都容易许多,所以使用主动转移):

  1. 在转移中保留 S i + 1 S_{i + 1} Si+1: 对于这种情况,我们希望找到 一个数 k k k 使 S 1 ⋯ i + 1 S_{1 \cdots i + 1} S1i+1 T 1 ⋯ k T_{1 \cdots k} T1k 配对。在寻找 k k k 的过程中,我们可以使用类似于 KMP 算法的 while 循环来寻找(即利用 Bonder 的性质,让已经匹配的部分尽量长)。在这种情况下, f i + 1 , k = f i , j f_{i + 1, k} = f_{i, j} fi+1,k=fi,j(注意当 k k k 0 0 0 时也需要转移)。

  2. 在转移中删除 S i + 1 S_{i + 1} Si+1: 这种情况下匹配的长度与转移前相同,即 f i + 1 , j = f i , j + 1 f_{i + 1, j} = f_{i, j} + 1 fi+1,j=fi,j+1

参考代码

#include<bits/stdc++.h>    //万能头
using namespace std;
const int NR = 1e4;
const int MR = 1e3;
const int KR = 2e2;
char s[NR + 10];
char t[MR + 10];
int p[MR + 10];
int dp[NR + 10][MR + 10];     //dp 数组
int f[NR + 10][KR + 10];      //记忆化
void KMP(char x[], int len){    //预处理 t 的前缀最大 Bonder 长
	p[0] = 0;
	p[1] = 0;
	for(int i = 2;i <= len;i++){
		int j = p[i - 1];
		while(j != 0 && x[j + 1] != x[i]){
			j = p[j];
		}
		p[i] = (x[j + 1] == x[i]  ? j + 1 : 0);
	}
	return ;
}
int cal(int j, char c){   //记忆化优化计算 k 
	if(f[j][c] != -1) return f[j][c];  //已经计算过则返回
	int k = j;      //否则计算
	while(k != 0 && c != t[k + 1]){  //利用 Bonder 的性质,让已经匹配的部分尽量长
		k = p[k];
	}
	return f[j][c] = k;  //计算完毕,返回
}

int main(){
   //初始化
	memset(dp, 0x3f3f3f3f, sizeof(dp));
	memset(f, -1, sizeof(f));
	dp[0][0] = 0;
   //读入
	scanf("%s%s", s + 1, t + 1);
	int n = strlen(s + 1), m = strlen(t + 1);
   //预处理
	KMP(t, m);
   //动态规划
	for(int i = 0;i < n;i++){
		for(int j = 0;j < m;j++){
			int k = cal(j, s[i + 1]);  //计算 k
        // 转移 1
			if(s[i + 1] == t[k + 1] && k + 1 != m){
				dp[i + 1][k + 1] = min(dp[i + 1][k + 1], dp[i][j]);  
			}
			else if(k + 1 != m) dp[i + 1][0] = min(dp[i + 1][0], dp[i][j]);   //注意当 $k$ 为 $0$ 时也需要转移
			dp[i + 1][j] = min(dp[i + 1][j], dp[i][j] + 1);   //转移 2
		}
	}
   //统计答案
	int Min = INT_MAX;
	for(int i = 0;i < m;i++){
		Min = min(Min, dp[n][i]);
	}
	printf("%d\n", Min);  //输出
	return 0;
}
  • 7
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值