PAT L3-020 至多删三个字符

本文探讨了一道关于字符串操作的问题,通过动态规划算法求解在给定字符串中至多删除3个字符后能得到多少种不同的字符串。文章详细解析了算法思路,包括递归公式和如何避免重复计算,提供了一个具体的实现代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

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

输入格式:

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

输出格式:

在一行中输出至多删掉其中 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"。

思路

dp[i][j]表示的从前i个字符中删除恰好j个,剩下字符构成的字符串有多少种不同的,在上面样例中就是:

dp[6][0]=1;

dp[6][1]=5;

dp[6][2]=9;

dp[6][3]=10;

答案就是这些值累加。

如何递推dp[i][j],下面这个不完全对的式子还是容易想到的:

dp[i][j]=dp[i-1][j]+dp[i-1][j-1]

dp[i-1][j]存储的是前i-1个字符里删除了j个之后能形成的字串个数,那如果我不删除第i个字符,前i个字符就还是只删除了j个字符,dp[i-1][j]的值就可以累加到dp[i][j]上;

dp[i-1][j-1]存储的时前i-1个字符删除了j-1个后能形成的字串个数,如果我接着把第i个字符删除,就相当于前i个字符删除了j个,那

dp[i-1][j-1]的值就可以累加到dp[i][j]上;

可是看似正确,会有重复计算

比如qwabda 一共6个字符长,这里假设递推式下标从1开始,dp[6][3]=dp[5][2]+dp[5][3];

而dp[5][2]是前5个里删2个,这肯定包括了删除第4第五个字符bd这种情况,然后再把第6个删除形成dp[6][3]的一员;

这相当于删除了bda 如右所示 qwabda

dp[5][3]里肯定包括了删除第3,4,5个字符的情况,如右所示 qwabda;

这两种删除方式都会留下 qwa这个字串,说明有重复情况被累加了;

如何减去重复的次数,每当递推到一个dp[i][j]时,就从第i-1到第1个字符里倒着找有没有等于第i个字符的,找到第一个后停下,假设这个位置是k,那么[k,i-1]和[k+1,i]这两种删除方式得到的剩下的串就是相同的,参考上面红色的部分,而且从[k+1,i-1]这段是两种删除方式中都会删掉的,所以[k,i]这段里有i-k个字符被删掉了,要满足dp[i][j],就还需要从[1,k-1]这段里面删掉(j-(i-k))个字符,而

dp[k-1][j-(i-k))]就是从前k-1个字符中删除(j-(i-k))个字符能形成多少不同字串,这个值贡献到了dp[i][j]的计算中,而且这个值在计算dp[i][j]累加时被加了两次,现在减掉一倍的它就可以了,然后就break;去递推下一个dp[i][j+1]或者是该dp[i+1][j];

为什么找到一个相同的后就break呢,比如qwwqabdaga这种,abda这里会需要去重,aga这里也会去重,代码里的循环是从左向右递推的,比dp[i][j]小的任意dp[i-k1][j-k2]都肯定是去过重了,在aga这部分去重时用到的前面的dp[i][j]一定是正确的,不会影响到后面。

#include <bits/stdc++.h>
#define ll long long
using namespace std;

ll dp[1000010][4];
char ch[1000010];

int main()
{
    scanf("%s",ch+1);
    int len=strlen(ch+1);
    for(int i=0;i<=len;i++)dp[i][0]=1;//任意字符串删去0个字符的方案数只有1个,就是保持本身
    for(int i=1;i<=len;i++)
    {
        for(int j=1;j<=3;j++)
        {
            dp[i][j]+=dp[i-1][j-1];//删第i个字符,从前i-1个字符中删去j-1个,这样一共删j个
            dp[i][j]+=dp[i-1][j];//不删第i字符,从前i-1个字符中删j个,这样还是一共删j个
            for(int k=i-1;k>0&&j>=i-k;k--)
            {
                if(ch[i]==ch[k])
                {
                    dp[i][j]-=dp[k-1][j-(i-k)];
                    break;
                }
            }
        }
    }
    printf("%lld\n",dp[len][0]+dp[len][1]+dp[len][2]+dp[len][3]);
    return 0;
}

### 关于L3-020的相关IT内容分析 目前提供的引用材料中并未直接提及"L3-020"的具体含义或关联内容。然而,通过推测以及结合常见的IT领域术语和问题分类,可以尝试解析可能涉及的内容。 #### 可能的方向一:算法竞赛中的题目编号 在某些编程比赛或者在线评测系统中(例如PAT、LeetCode等),通常会使用类似的命名方式来标记特定的题目。例如,“L3可能是难度等级的缩写,而“020”则是具体的题目序号。基于此假设: 1. **特殊数据结构设计** 如果类似于引用[4]提到的堆栈扩展功能[^4],那么L3-020可能会涉及到一种特殊的抽象数据类型的实现。比如队列、双端队列或其他变种的数据结构,要求支持额外的功能操作。 下面是一个简单的例子展示如何实现带附加功能的数据结构: ```python class SpecialStack: def __init__(self): self.stack = [] def push(self, value): """Push an element onto the stack.""" self.stack.append(value) def pop(self): """Pop the top element from the stack.""" if not self.is_empty(): return self.stack.pop() return None def median(self): """Return the median of all elements in the stack.""" sorted_stack = sorted(self.stack) n = len(sorted_stack) mid = n // 2 if n % 2 == 0: return sorted_stack[mid - 1] else: return sorted_stack[mid] def is_empty(self): """Check if the stack is empty.""" return len(self.stack) == 0 ``` 2. **图论或动态规划类问题** 类似于引用[1]所描述的比赛场景[^1],L3-020可能是一道关于路径优化、最短路计算或者是状态转移方程求解的问题。这类问题往往需要选手具备扎实的基础知识,并能够灵活运用各种经典算法模型加以解决。 #### 方向二:网络服务配置文件片段 从引用[2]来看,它展示了Neutron L3 Agent 的启动脚本代码[^2]。虽然这里的标识符并不完全匹配目标查询项(L3-020),但如果考虑实际应用场景的话,也许存在某种形式上的相似之处。例如,在部署云计算环境下的虚拟路由器实例过程中需要用到此类参数设置方法。 --- ### 总结说明 由于缺乏足够的上下文信息,上述两种解释均属于合理猜测范畴之内。为了更精准定位所需资源,请提供更多背景资料以便进一步探讨。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值