1397. 找到所有好字符串(kmp理解 + 数位dp)(好题!!!)

题目链接

首先特别感谢 灵茶山艾府 的 这篇文章,让我重新学了一下数位dp!
以及 官方题解!

没有学过kmp的小伙伴建议先去学一下。

这个题最重要最重要的就是状态的表示!

分析

首先是这个题目给的是一个范围[l,r]内求个数,一般来讲,数位dp都能利用前缀和的思想(区间有连续性):
我们先求出[0,l-1]和[0,r]范围内合法的个数,然后ans[0,r] - ans[0,l-1]就行了。

然后还是一样的惯例,套用之前的模板;
但是我们发现我们在转移的过程中,可能需要记录前面构造的字符串;
但是又没有一种比较好的状态表示方法,比如状态压缩之类的…

此时我们回到题目本身,我们在构造的过程中只需要知道已构造的字符串和evil是否匹配就行;
结合数位dp从高位到地位的dp方式,其实在这个过程中我们相当于同时在遍历构造的字符串,然后同时判断和evil是否完全匹配;
这个过程是不是很熟悉???
没错,就很像kmp算法中的匹配过程!遍历长串然后和短串匹配!因此我们可以运用kmp算法。

kmp在匹配时的具体细节就是:
每次遍历长串s1[0 ~ n]的一个位置i,然后利用短串evil[0 ~ m]的next[]求出最大的j;
使得长串s1[0 ~ i]长度为j的后缀 和 短串evil长度为j的前缀匹配;
如果j到了短串evil的最后一个字符+1,则说明匹配成功。

因此,我们在记忆化搜索的过程中,也就是构造每一个字符时,加一个j的状态(意思如上所示),这里记为state;
对于记忆化搜索的状态如下:(板子参数)
i:当前填到哪一位;
state:构造到i-1位时和短串evil的最大匹配(意思同上);
is_litmit: 前一位i-1是否填到了s[i-1](上界);
如果为true:则表示当前位i能填的上界是s[i],否则是‘z’;

具体过程如下:

  1. 递归出口(具体看代码)
  2. 求出上界,枚举构造的字母ch,记忆化
    此时就要求出构造ch时的新状态new_state,具体其实就是kmp的匹配过程;

特别注意的是这里对 匹配字符串时的状态转移也进行了记忆化。

代码

#include<iostream>
#include<queue>
#include<cstring>
#include<vector>
#include<stdio.h>
#include<map>
#include<algorithm>
#include<deque>
#include<stack>
#include<set>
// #include <unordered_map>
#include<math.h>
#include<string.h>
#define IOS ios::sync_with_stdio(false),cin.tie(0);
using namespace std;
 
#define pb push_back
#define coutl cout<<"------------"<<endl;
#define fi first
#define se second

#define ire(x) scanf("%d",&x)
#define iire(a,b) scanf("%d %d",&a,&b)
#define lre(x) scanf("%lld",&x)
#define llre(a,b) scanf("%lld %lld",&a,&b)
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define endl "\n"
#define PI acos(-1.0)
// #define int long long
// #define double long double
typedef long long ll;
typedef unsigned long long ull;
      
typedef pair<int, int> PII;
typedef pair<double, int> PDI;
typedef pair<ll, ll> PLL;
typedef pair<double, double> PDD;
typedef pair<double, pair<int, double> > PDID;
typedef pair<char, char> PCC;
typedef pair<char, pair<int, int> > PCII;
typedef pair<int, pair<int, int> > PIII;
typedef pair<int, pair<int, pair<int, int> > > PIIII;
typedef pair<ll, pair<int, int> > PLII;

const int maxn = 1e6 + 7;
const int N = 2e6 + 7;
const int M = 4e6 + 7;
const int mod = 1e9 + 7;
const int inv = mod - mod/2;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const double pi = acos(-1);
const double eps = 1e-8;
  
ll gcd(ll a,ll b) {return b==0 ? a : gcd(b,a%b);}
ll lcm(ll a,ll b) {return a*b / gcd(a,b);}
ll qmi(ll a,ll b,ll p) {ll ans = 1; while(b) { if(b & 1) ans = ans * a % p; a = a * a % p; b >>= 1; } return ans;}
int lowbit(int x) {return x & (-x);}

//从这里开始----------------------------------------------------------------------------------

vector<int> ne;	//next数组,这里是从0开始的
string s1,s2,evil;
int m;

ll dp[550][55][2];
int mp[550][55];	//匹配字符串时的状态转移的记忆化

int get_state(int state,char ch)
{
	if(mp[state][ch-'a'] != -1) return mp[state][ch-'a'];
	
	//开始匹配
	while(state && evil[state] != ch) state = ne[state-1];
	
	mp[state][ch-'a'] = (evil[state] == ch ? state + 1 : 0);
	
	return mp[state][ch-'a'];
}

//数位dp,记忆化搜索
ll dfs(int i,int state,bool is_litmit,string s)
{
	//先判不合法的情况,前面构造的字符串已经出现和evil匹配
	if(state == evil.size()) return 0;	
	if(i == s.size()) return 1;	//合法,构造完了
	if(dp[i][state][is_litmit] != -1) return dp[i][state][is_litmit];
	
	ll ans = 0;
	char up = is_litmit ? s[i] : 'z';
	for(auto ch='a';ch<=up;ch++)
	{
		int new_state = get_state(state,ch);
		ans = (ans + dfs(i+1,new_state,is_litmit && ch==up,s)) % mod;
	}
	
	dp[i][state][is_litmit] = ans;
	
	return ans;
}

class Solution {
public:
    int findGoodStrings(int n, string _s1, string _s2, string _evil) {
		
		s1 = _s1;
		s2 = _s2;
		evil = _evil;
		
		int m = evil.size();
		vector<int> v(m);
		for(int i=1;i<m;i++)	//求next数组,从0开始
		{
			int j = v[i-1];
			while(j && evil[j] != evil[i]) j = v[j-1];
			
			if(evil[j] == evil[i]) v[i] = j+1;
		}
		ne = v;
		
		//求一下从aaa...到s2的合法字符串(不包含evil)个数
		memset(dp,-1,sizeof dp);
		memset(mp,-1,sizeof mp);
        ll ans2 = dfs(0,0,true,s2);
		
		//求一下从aaa...到s1的合法字符串(不包含evil)个数
        memset(dp,-1,sizeof dp);
		memset(mp,-1,sizeof mp);
		ll ans1 = dfs(0,0,true,s1);
		
		ll ans = (ans2-ans1+mod) % mod;	//利用前缀和减一下
		
		//判断一下s1是否合法,如果合法则加回来s1
        int f = 1;
        for(int i=0;i+m-1<s1.size();i++)
        {
            string s = s1.substr(i,m);
            if(s == evil)
            {
                f = 0;
                break;
            }
        }
        if(f) ans++;

		return ans;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值