zoukankan      html  css  js  c++  java
  • DP与KMP的结合

    闲话

    其实我也不太知道这个套路叫啥,他的过程就是DP和KMP的结合,毕竟是通过KMP来加速暴力匹配的过程,标题就写成KMP优化DP了.与之对应的还有一种AC自动机上优化的,其实两个套路一模一样,这里就只展示两个题,AC自动机的如果之后有空也会补上来.

    设计密码

    原题链接:Acwing1052

    (由于acwing的题目上锁了,可能看不到,这个题是Indeedtokyo的19面试题,可能没得地方给评测)

    题目大意:要求你构造一个长度为(n)的字符串(s),并且不包含子串(t).求(s)的方案数,对(10^9+7)取模.

    此处不包含的定义是连续的一段不等于(t),不能是断开的几段拼起来是(t).

    数据范围:

    (1 leq n leq 50)

    (1 leq |t| leq n)

    思路

    一个比较直接的想法肯定就是枚举了,不过既然方案数需要取模,说明方案数非常大,这个时候往往指向使用(dp)求方案数的思路.考虑设计状态:首先第一维肯定是保存当前构造到哪一位,同时我们还要知道现在具体匹配到(t)中的哪一个位置了,也就是当前这个位置肯定会和(t)产生字符串的匹配,问题就是现在连续的匹配到了谁的问题,这个东西肯定不好直接求,直接丢状态里.

    那么可以写一下(dp)的具体流程了:

    • 状态:(f[i][j])表示当前在(s)的第(i)位,并且(s[i])这一位填上某个元素之后,与(t)的匹配长度是(j)的方案数.
    • 入口:(f[0][0]=0)其他全部置为(-infin).
    • 转移:考虑(f[i][j])是由谁转移而来的是非常麻烦的,这里考虑当前这个状态(f[i][j])可以转移到哪个状态,那么枚举下一位,也就是(i+1)这一位具体要填什么字符,拿他去进一步和(t)进行匹配,匹配的一个新位置记作(u),那么下一个状态就是(f[i + 1][u]).显然当(u==|t|)时意味着(s)里面有一个子串(t)存在了,这种情况必须要删除.往下就可以写出方程形式:(f[i + 1][u] += f[i][j]).
    • 出口:首先要做到最后一位,第一维肯定是(n),第二维只要保证不取(|t|)就可以了,直接把方案总和起来.

    接着可以发现,这个枚举(s[i + 1])的取值,并且往后匹配的过程恰好就是拿(t)去做匹配,那么类似KMP处理就可以了.

    不过这里其实有个出口第一维的取值的细节问题,下一个问题会考虑到,这个问题不太需要.

    代码

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N = 55,MOD = 1e9 + 7;
    char s[N],T[N];
    int succ[N],f[N][N];
    int main()
    {
    	int n;scanf("%d",&n);
    	scanf("%s",T + 1);
    	int m = strlen(T + 1);
    	for(int i = 2,j = 0;i <= m;++i)
    	{
    		while(j && T[i] != T[j + 1])	j = succ[j];
    		if(T[i] == T[j + 1])	++j;
    		succ[i] = j;
    	}
    	f[0][0] = 1;
    	for(int i = 0;i <= n;++i)
    	{
    		for(int j = 0;j < m;++j)
    		{
    			for(char k = 'a';k <= 'z';++k)
    			{
    				int u = j;
    				while(u && (u == n || k != T[u + 1])) u = succ[u];
    				if(k == T[u + 1])	++u;
    				if(u < m)	f[i + 1][u] = (f[i + 1][u] + f[i][j]) % MOD;
    			}
    		}
    	}
    	int res = 0;
    	for(int i = 0;i < m;++i)	res = (res + f[n][i]) % MOD;
    	printf("%d",res);
        return 0;
    }
    
    

    CF1163 D Mysterious Code

    题目大意:有两个串(s)(t)只包含小写字符,另有一个字符(c),其中包含小写字符或者*.现在要求你把*全部填上小写字符,并且规定两个字符串的牛逼操作(f(c,s)),他的值是(s)串在(c)中的出现次数,注意这里的出现次数跟上面那道题是一样连续的,而不是断开的.求(f(c,s) - f(c,t))的最大值.所有*位置必须填上字符,不可以空.

    数据范围:

    (1 leq |c| leq 1000)

    (1 leq |s| ,|t| leq 50,s eq t)

    思路

    注意到较小的数据范围,接下来考虑直接dp:

    • 状态:(f[i][j][k])表示当前(c)做到第(i)位,在(s)中的匹配位置是(j),在(t)中的匹配位置是(k).取得的(f(c,s)-f(c,t))的所有可能取值中的最大值.

    • 入口:(f[1][0]=0)其他全部(-infin).

    • 转移:与上面一致,如果当前这一位是*则直接枚举取值,否则就直接按原有的值做.这里分别对两个串做匹配,记(j`,k`)分别是在(s)(t)中下一位匹配到的新位置,只有当他们分别等于另一串的长度的时候才意味着匹配成功,当(s)匹配成功时权值增加一,同样(t)则减少一.直接转移即可.

    • 出口:第一维取(n+1)其他两维任选,取最大值即可.

    这里的出口是有差别的,最开始把(c)这个串挪动成(1)开始的话,那么如果有串是枚举到(c)的最后一位,也就是(i=n)时才匹配上,那么他转移的时候,这一位本身要权值增加或者权值减少的,但是会把这个修改丢到(n+1)位上,所以最后一步等于说把本来该在这一位的移动到了下一位去,尽管下一位(c)并没有值,这是这种转移方式带来的.当然你也可以选择不把(c)设置从1开始,枚举的时候也从([0,n-1]),这个时候答案就要在(n)处统计了,可能看的好看点.

    代码

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    #define forn(i,x,n) for(int i = x;i <= n;++i)	
    
    const int N = 55,M = 1005;
    char c[M];
    int f[M][N][N];
    struct kmp
    {
    	char s[N],next[N];
    	int n;
    	void build()
    	{
    		next[1] = 0;
    		for(int i = 2,j = 0;i <= n;++i)
    		{
    			while(j > 0 && s[i] != s[j + 1]) j = next[j];
    			if(s[i] == s[j + 1]) ++j;
    			next[i] = j;
    		}
    	}
    }s,t;
    
    int main()
    {
    	scanf("%s",c + 1);
    	int n = strlen(c + 1);
    	scanf("%s%s",s.s + 1,t.s + 1);
    	s.n = strlen(s.s + 1);t.n = strlen(t.s + 1);
    	s.build();t.build();
    	
    	memset(f,-0x3f,sizeof f);
    	f[1][0][0] = 0;
    	forn(i,1,n)	forn(j,0,s.n) forn(k,0,t.n)
    	{
    		for(char u = (c[i] == '*' ? 'a' : c[i]);u <= (c[i] == '*' ? 'z' : c[i]);++u)
    		{
    			int j_ = j,k_ = k;
    			while(j_ > 0 && (j_ == s.n || u != s.s[j_ + 1]))	j_ = s.next[j_];
    			if(u == s.s[j_ + 1])	++j_;
    			while(k_ > 0 && (k_ == t.n || u != t.s[k_ + 1]))	k_ = t.next[k_];
    			if(u == t.s[k_ + 1])	++k_;
    			
    			int ct = (j_ == s.n) - (k_ == t.n);
    			f[i + 1][j_][k_] = max(f[i + 1][j_][k_],f[i][j][k] + ct);
    		}
    	}
    	int res = -1e9;
    	forn(j,0,s.n)	forn(k,0,t.n)	res = max(res,f[n + 1][j][k]);
    	printf("%d",res);
        return 0;
    }
    
    
  • 相关阅读:
    java连接oracle数据库调用存储过程实现分页查询(emp为例)
    git 记录
    Angular记录
    “nth-child”选择器的优先级高于伪类选择器“:hover”
    Angular获取父组件传值-set,get
    Vue3.0路由 -- 学习笔记
    Vue学习笔记
    java-关于List删除元素的出错问题
    CSS选择器-复合选择器
    Angular 路由返回
  • 原文地址:https://www.cnblogs.com/HotPants/p/14285574.html
Copyright © 2011-2022 走看看