给定一个全部由小写英文字母组成的字符串,允许你至多删掉其中 3 个字符,结果可能有多少种不同的字符串?

输入格式:

输入在一行中给出全部由小写英文字母组成的、长度在区间 [4, 1] 内的字符串。

输出格式:

在一行中输出至多删掉其中 3 个字符后不同字符串的个数。

输入样例:

  1. ababcc

输出样例:

  1. 25

提示:

删掉 0 个字符得到 "ababcc"。

删掉 1 个字符得到 "babcc", "aabcc", "abbcc", "abacc" 和 "ababc"。

删掉 2 个字符得到 "abcc", "bbcc", "bacc", "babc", "aacc", "aabc", "abbc", "abac" 和 "abab"。

删掉 3 个字符得到 "abc", "bcc", "acc", "bbc", "bac", "bab", "aac", "aab", "abb" 和 "aba"。

解法:

前置技能:求一个序列中所有的不同子序列个数。

eg:FZU - 2129

设dp[i]为序列a的前i个元素所组成的不同子序列个数,则有状态转移方程:$dp[i]=\left\{\begin{matrix}\begin{aligned}&2dp[i-1]+1,pre[a[i]]=-1\\&2dp[i-1]-dp[pre[a[i]]-1],pre[a[i]]\neq -1\end{aligned}\end{matrix}\right.$

其中pre[a[i]]表示a[i]前面第一个和a[i]相同的元素的下标。

解释:第i个元素a[i]有两种选择:选或不选。

若不选a[i],则dp[i]继承dp[i-1]的全部子序列,因此有dp[i]+=dp[i-1]。

若选a[i],则dp[i]在dp[i-1]的全部子序列的尾部填加了个元素a[i],因此仍有dp[i]+=dp[i-1]。但这样会有很多重复的序列,因此要去重,即去掉前面和a[i]相同的元素之前的序列(因为它们加上a[i]形成的序列已经被算过了),因此有dp[i]-=dp[pre[a[i]]-1]。特别地,如果a[i]前面没有与a[i]相同的元素,那么没有重复的序列,并且a[i]自己单独形成一个新序列,此时dp[i]++。

  1. #include<cstdio>
  2. #include<cstring>
  3. using namespace std;
  4. typedef long long ll;
  5. typedef double db;
  6. const int N=1e6+,mod=1e9+;
  7. int a[N],n,dp[N],pre[N];
  8. int main() {
  9. while(scanf("%d",&n)==) {
  10. memset(pre,-,sizeof pre);
  11. for(int i=; i<=n; ++i)scanf("%d",&a[i]);
  12. dp[]=;
  13. for(int i=; i<=n; ++i) {
  14. dp[i]=(ll)dp[i-]*%mod;
  15. if(~pre[a[i]])dp[i]=((ll)dp[i]-dp[pre[a[i]]-])%mod;
  16. else dp[i]=(dp[i]+)%mod;
  17. pre[a[i]]=i;
  18. }
  19. printf("%d\n",(dp[n]+mod)%mod);
  20. }
  21. return ;
  22. }

回到正题,此题是上题的升级版,等价于求一个长度为n的序列中长度为n,n-1,n-2,n-3的不同子序列个数之和。

基本思路是一致的,只需要在上述代码的基础上稍作改动即可。

设dp[i][j]为前i个元素删了j个元素所形成的子序列个数,则有$dp[i][j]=\left\{\begin{matrix}\begin{aligned}&dp[i-1][j-1]+dp[i-1][j],pre[a[i]]=-1,j\neq i-1\\&dp[i-1][j-1]+dp[i-1][j]+1,pre[a[i]]=-1,j=i-1\\&dp[i-1][j-1]+dp[i-1][j]-dp[pre[a[i]]-1][j-(i-pre[a[i]])],pre[a[i]]\neq -1\end{aligned}\end{matrix}\right.$

推导过程类似,注意j的变化即可。

  1. #include<cstdio>
  2. #include<cstring>
  3. using namespace std;
  4. typedef long long ll;
  5. typedef double db;
  6. const int N=1e6+;
  7. char a[N];
  8. int n,pre[];
  9. ll dp[N][];
  10. int main() {
  11. memset(pre,-,sizeof pre);
  12. scanf("%s",a+),n=strlen(a+);
  13. for(int i=; i<=n; ++i) {
  14. for(int j=; j<=; ++j) {
  15. if(j>)dp[i][j]+=dp[i-][j-];
  16. dp[i][j]+=dp[i-][j];
  17. if(~pre[a[i]]&&j>=i-pre[a[i]])dp[i][j]-=dp[pre[a[i]]-][j-(i-pre[a[i]])];
  18. else if(i==j+)dp[i][j]++;
  19. }
  20. pre[a[i]]=i;
  21. }
  22. printf("%lld\n",dp[n][]+dp[n][]+dp[n][]+dp[n][]);
  23. return ;
  24. }

还有另一种解法是利用序列自动机,很简单,设go[i][j]为第i个元素后第一个元素j出现的位置,先用类似dp的方式建立自动机,则问题转化成了一个DAG上的dp问题。

但是由于序列自动机空间消耗较大,直接dfs可能会爆内存,比如这样:

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. typedef long long ll;
  4. typedef double db;
  5. const int N=1e6+,M=;
  6. char s[N];
  7. int n,go[N][M],dp[N][];
  8. void build() {
  9. memset(go[n],,sizeof go[n]);
  10. for(int i=n-; i>=; --i)memcpy(go[i],go[i+],sizeof go[i]),go[i][s[i]-'a']=i+;
  11. }
  12. int dfs(int u,int k) {
  13. if(k>)return ;
  14. int& ret=dp[u][k];
  15. if(~ret)return ret;
  16. ret=(k+(n-u)<=);
  17. for(int i=; i<M; ++i)if(go[u][i])ret+=dfs(go[u][i],k+go[u][i]-u-);
  18. return ret;
  19. }
  20. int main() {
  21. scanf("%s",s),n=strlen(s);
  22. build();
  23. memset(dp,-,sizeof dp);
  24. printf("%d\n",dfs(,));
  25. return ;
  26. }

解决方法是自底而上,一遍dp一遍更新go数组,成功AC:

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. typedef long long ll;
  4. typedef double db;
  5. const int N=1e6+,M=;
  6. char s[N];
  7. int n,go[M];
  8. ll dp[N][];
  9. int main() {
  10. scanf("%s",s),n=strlen(s);
  11. dp[n][]=dp[n][]=dp[n][]=dp[n][]=;
  12. for(int i=n-; i>=; --i) {
  13. go[s[i]-'a']=i+;
  14. for(int j=; j<=; ++j) {
  15. dp[i][j]=(j+(n-i)<=);
  16. for(int k=; k<M; ++k)if(go[k]&&j+go[k]-i-<=)dp[i][j]+=dp[go[k]][j+go[k]-i-];
  17. }
  18. }
  19. printf("%lld\n",dp[][]);
  20. return ;
  21. }

虽然序列自动机的功能比较强大,但时间和空间的消耗都与元素集合的大小有关,因此当元素集合过大的时候,可能就并不吃香了~~

PTA L3-020 至多删三个字符 (序列dp/序列自动机)的更多相关文章

  1. pta l3-20(至多删三个字符)

    题目链接:https://pintia.cn/problem-sets/994805046380707840/problems/994805046946938880 题意:给定一个长度<=106 ...

  2. L3-020 至多删三个字符 (30 分)(DP)

    题目链接:https://pintia.cn/problem-sets/994805046380707840/problems/994805046946938880 学习地址: 2018CCCC-L3 ...

  3. L3-020 至多删三个字符 (30 分)

    给定一个全部由小写英文字母组成的字符串,允许你至多删掉其中 3 个字符,结果可能有多少种不同的字符串? 输入格式: 输入在一行中给出全部由小写英文字母组成的.长度在区间 [4, 1] 内的字符串. 输 ...

  4. PAT L3-020 至多删三个字符

    https://pintia.cn/problem-sets/994805046380707840/problems/994805046946938880 给定一个全部由小写英文字母组成的字符串,允许 ...

  5. L3-020 至多删三个字符 (30 分) 线性dp

    给定一个全部由小写英文字母组成的字符串,允许你至多删掉其中 3 个字符,结果可能有多少种不同的字符串? 输入格式: 输入在一行中给出全部由小写英文字母组成的.长度在区间 [4, 1] 内的字符串. 输 ...

  6. PTA 团体程序设计天梯赛 L3-020 至多删三个字符

    $f[i][j]$表示到第$i$个字符,已经删去了$j$个字符的方案数. 显然的转移: $f[i][j] = f[i - 1][j] + f[i - 1][j - 1]$ 但是这样会有重复,我们考虑什 ...

  7. [leetcode]680. Valid Palindrome II有效回文II(可至多删一原字符)

    Given a non-empty string s, you may delete at most one character. Judge whether you can make it a pa ...

  8. PTA 最多删除3个字符(DP) - 30分

    给定一个全部由小写英文字母组成的字符串,允许你至多删掉其中 3 个字符,结果可能有多少种不同的字符串? 输入格式: 输入在一行中给出全部由小写英文字母组成的.长度在区间 [4, 1] 内的字符串. 输 ...

  9. windows自带记事本导致文本文件(UTF-8编码)开头三个字符乱码问题

    在windows平台下,使用系统的记事本以UTF-8编码格式存储了一个文本文件,但是由于Microsoft开发记事本的团队使用了一个非常怪异的行为来保存UTF-8编码的文件,它们自作聪明地在每个文件开 ...

随机推荐

  1. Struct2小组开发简单命名规范

    基本原则或者说理念:简单就是美 1.数据库名:项目名 2.表名:_model名 3.字段:和model中的属性名一致(不要和数据库名冲突) 4.用层来划分包:com.liying.bbs.action ...

  2. Python读写改Excel的方法

    (注:本文部分内容摘自互联网,由于作者水平有限,不足之处,还望留言指正.) 面对疾风吧. 回首往昔,更进一步. 且随疾风前行,身后一许流星. 正文: 数据处理是Python的一大应用场景,而 Exce ...

  3. [CTSC2008]祭祀

    题目描述 在遥远的东方,有一个神秘的民族,自称Y族.他们世代居住在水面上,奉龙王为神.每逢重大庆典, Y族都会在水面上举办盛大的祭祀活动.我们可以把Y族居住地水系看成一个由岔口和河道组成的网络.每条河 ...

  4. jQuery多级联动美化版Select下拉框

    在线演示 本地下载

  5. java鲁棒性(健壮性)

    java能检测编译和运行时的错误 java自己操作内存减少了内存出错的可能 java实现了真数组,避免了覆盖数据的可能 Java不支持指针操作,大大减少了错误发生的可能性 ... 备注: Java能运 ...

  6. Python引用多个模块,调用模块中的函数时,要注意的地方

    转自:http://blog.csdn.net/yjk13703623757/article/details/70237463 python模块是”从下到上”导入(import)的. 例如: a.py ...

  7. Jave基础之选择排序

    选择排序(Selection sort) 基本介绍 选择排序: 每一次从未排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余未排序的元素中选出最小(或最大的)放在已排序序 ...

  8. Mysql 基本用法

    Java中两种常用的数据库: MYSQL     Oracle MYSQL  :开源免费的数据库,小型的数据库.由瑞典MySQL AB 公司开发,适合中小企业使用,由C语言和C++编写的.已经被Ora ...

  9. JSR规范整理

    Web Service技术 Java Date与Time API ( JSR 310) Java API for RESTful Web Services (JAX-RS) 1.1 (JSR 311) ...

  10. Entity Framework 6:专家版本

    随着 Entity Framework 最新主版本 EF6 的推出,Microsoft 对象关系映射 (ORM) 工具达到了新的专业高度,与久负盛名的 .NET ORM 工具相比已不再是门外汉. EF ...