PTA L3-020 至多删三个字符 (序列dp/序列自动机)
给定一个全部由小写英文字母组成的字符串,允许你至多删掉其中 3 个字符,结果可能有多少种不同的字符串?
输入格式:
输入在一行中给出全部由小写英文字母组成的、长度在区间 [4, 1] 内的字符串。
输出格式:
在一行中输出至多删掉其中 3 个字符后不同字符串的个数。
输入样例:
ababcc
输出样例:
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]++。
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
typedef double db;
const int N=1e6+,mod=1e9+;
int a[N],n,dp[N],pre[N];
int main() {
while(scanf("%d",&n)==) {
memset(pre,-,sizeof pre);
for(int i=; i<=n; ++i)scanf("%d",&a[i]);
dp[]=;
for(int i=; i<=n; ++i) {
dp[i]=(ll)dp[i-]*%mod;
if(~pre[a[i]])dp[i]=((ll)dp[i]-dp[pre[a[i]]-])%mod;
else dp[i]=(dp[i]+)%mod;
pre[a[i]]=i;
}
printf("%d\n",(dp[n]+mod)%mod);
}
return ;
}
回到正题,此题是上题的升级版,等价于求一个长度为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的变化即可。
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
typedef double db;
const int N=1e6+;
char a[N];
int n,pre[];
ll dp[N][];
int main() {
memset(pre,-,sizeof pre);
scanf("%s",a+),n=strlen(a+);
for(int i=; i<=n; ++i) {
for(int j=; j<=; ++j) {
if(j>)dp[i][j]+=dp[i-][j-];
dp[i][j]+=dp[i-][j];
if(~pre[a[i]]&&j>=i-pre[a[i]])dp[i][j]-=dp[pre[a[i]]-][j-(i-pre[a[i]])];
else if(i==j+)dp[i][j]++;
}
pre[a[i]]=i;
}
printf("%lld\n",dp[n][]+dp[n][]+dp[n][]+dp[n][]);
return ;
}
还有另一种解法是利用序列自动机,很简单,设go[i][j]为第i个元素后第一个元素j出现的位置,先用类似dp的方式建立自动机,则问题转化成了一个DAG上的dp问题。
但是由于序列自动机空间消耗较大,直接dfs可能会爆内存,比如这样:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
const int N=1e6+,M=;
char s[N];
int n,go[N][M],dp[N][];
void build() {
memset(go[n],,sizeof go[n]);
for(int i=n-; i>=; --i)memcpy(go[i],go[i+],sizeof go[i]),go[i][s[i]-'a']=i+;
}
int dfs(int u,int k) {
if(k>)return ;
int& ret=dp[u][k];
if(~ret)return ret;
ret=(k+(n-u)<=);
for(int i=; i<M; ++i)if(go[u][i])ret+=dfs(go[u][i],k+go[u][i]-u-);
return ret;
}
int main() {
scanf("%s",s),n=strlen(s);
build();
memset(dp,-,sizeof dp);
printf("%d\n",dfs(,));
return ;
}
解决方法是自底而上,一遍dp一遍更新go数组,成功AC:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
const int N=1e6+,M=;
char s[N];
int n,go[M];
ll dp[N][];
int main() {
scanf("%s",s),n=strlen(s);
dp[n][]=dp[n][]=dp[n][]=dp[n][]=;
for(int i=n-; i>=; --i) {
go[s[i]-'a']=i+;
for(int j=; j<=; ++j) {
dp[i][j]=(j+(n-i)<=);
for(int k=; k<M; ++k)if(go[k]&&j+go[k]-i-<=)dp[i][j]+=dp[go[k]][j+go[k]-i-];
}
}
printf("%lld\n",dp[][]);
return ;
}
虽然序列自动机的功能比较强大,但时间和空间的消耗都与元素集合的大小有关,因此当元素集合过大的时候,可能就并不吃香了~~
PTA L3-020 至多删三个字符 (序列dp/序列自动机)的更多相关文章
- pta l3-20(至多删三个字符)
题目链接:https://pintia.cn/problem-sets/994805046380707840/problems/994805046946938880 题意:给定一个长度<=106 ...
- L3-020 至多删三个字符 (30 分)(DP)
题目链接:https://pintia.cn/problem-sets/994805046380707840/problems/994805046946938880 学习地址: 2018CCCC-L3 ...
- L3-020 至多删三个字符 (30 分)
给定一个全部由小写英文字母组成的字符串,允许你至多删掉其中 3 个字符,结果可能有多少种不同的字符串? 输入格式: 输入在一行中给出全部由小写英文字母组成的.长度在区间 [4, 1] 内的字符串. 输 ...
- PAT L3-020 至多删三个字符
https://pintia.cn/problem-sets/994805046380707840/problems/994805046946938880 给定一个全部由小写英文字母组成的字符串,允许 ...
- L3-020 至多删三个字符 (30 分) 线性dp
给定一个全部由小写英文字母组成的字符串,允许你至多删掉其中 3 个字符,结果可能有多少种不同的字符串? 输入格式: 输入在一行中给出全部由小写英文字母组成的.长度在区间 [4, 1] 内的字符串. 输 ...
- PTA 团体程序设计天梯赛 L3-020 至多删三个字符
$f[i][j]$表示到第$i$个字符,已经删去了$j$个字符的方案数. 显然的转移: $f[i][j] = f[i - 1][j] + f[i - 1][j - 1]$ 但是这样会有重复,我们考虑什 ...
- [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 ...
- PTA 最多删除3个字符(DP) - 30分
给定一个全部由小写英文字母组成的字符串,允许你至多删掉其中 3 个字符,结果可能有多少种不同的字符串? 输入格式: 输入在一行中给出全部由小写英文字母组成的.长度在区间 [4, 1] 内的字符串. 输 ...
- windows自带记事本导致文本文件(UTF-8编码)开头三个字符乱码问题
在windows平台下,使用系统的记事本以UTF-8编码格式存储了一个文本文件,但是由于Microsoft开发记事本的团队使用了一个非常怪异的行为来保存UTF-8编码的文件,它们自作聪明地在每个文件开 ...
随机推荐
- CWinApp类CMultiDocTemplate类CDocument类CView类的关系
转自:http://blog.csdn.net/bboot/article/details/26884011 不得不转,瞬间搞清了很多问题,短小精悍 1.CWinApp类 它包含并管理着应用程序的 ...
- hadoop22---wait,notify
vv wait和notify,是要加syschronized的,是要获取锁的,wait是释放控制权,别的线程就可以执行了,notify和notifyall是通知其他线程执行.
- C语言单元测试框架--EmbedUnit
1.简介 Embedded Unit是个纯标准c构建的单元测试框架,主要用在嵌入式c的单体测试上,其主要特点是不依赖于任何C的标准库,所有的对象都是静态分配. 最早这个项目托管在SourceForge ...
- Refseq,accssion #,gi ,Ensembl的关系
accession编号的分子类型代号: Ensembl是2000年就开始开发的基因组自动注释软件,起初是只对真核生物基因组,2009年后开始对植物,细菌等开放.既然要注释,就要有注释对象(基因,转录本 ...
- MapReduce:输出是一个文本文件,每一行第一个数字式行标,第二个数字是输入文件中每一行除行标外数字的平均值,且整数不保留小数,小数保留两位小数点
有时候你会遇到这样的问题:你有一个表格,给出了每个人在十二月,一月和二月的收入. 表格如下: 姓名 一月 二月 三月 楚乔 200 314 3500 宇文玥 2000 332 ...
- ErrorHandling in asp.net web api
https://docs.microsoft.com/en-us/aspnet/web-api/overview/error-handling/exception-handling https://d ...
- Action Results in Web API 2
https://docs.microsoft.com/en-us/aspnet/web-api/overview/getting-started-with-aspnet-web-api/action- ...
- mysql——主键自动增长&唯一索引
首先说一下主键和唯一索引的区别 主键:一个数据库的一张表有且仅有一个主键,而且主键不能重复 唯一索引:一个数据库的一张表上唯一索引可以有多个,只是所在唯一索引上的值不能重复,这一点和主键一样 下面我们 ...
- windchill系统——开发_角色管理——增加角色
步骤如下 ResourceBuild wt.project.RoleRB ant -f codebase/MakeJar.xml 这样就完成了,接下来看看结果
- qtjambi_ZC
loadJambiJniLibrary --> loadLibrary --> loadNativeLibrary --> loadLibrary_helper class QApp ...