PTA L3-020 至多删三个字符——详细解析

题目大意

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

解析

  题目用到的是动态规划的思想,但是我一开始没想到,看了解析也不明白,最后苦思冥想才明白,只能说菜就多练。
  上面的那篇解析,说明文字比较少,这里我整理一下自己的思路。首先定义状态:dp[i][j]表示 s s s 的前 i i i 个字符组成的字符串里面要删除 j j j 个字符,得到的字符串有多少种。如果记s[a:b] s s s 的第 a a a 个(含)到第 b b b 个(不含)字符所组成的子串。
  现在需要得到状态转移方程。显然,当删除的这 j j j 个字符包含s[i]的时候,对答案的贡献是dp[i - 1][j - 1];如果删除的 j j j 个字符中不包含s[i],那么对答案的贡献就是dp[i - 1][j]。于是可以写出状态转移方程:
d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + d p [ i − 1 ] [ j ] (1) \mathit{dp}[i][j]=\mathit{dp}[i-1][j-1]+\mathit{dp}[i-1][j]\tag1 dp[i][j]=dp[i1][j1]+dp[i1][j](1)

考虑删除 s[i]
考虑不删除 s[i]
dp[i][j]
再从 s[1:i] 中删掉 j-1 个字符
从 s[1:i] 中删掉 j 个字符
dp[i-1][j-1]
dp[i-1][j]

但是,这个状态转移方程是错误的,因为有可能会有重复计算的情况。正如字符串s = "abcac",如果要求dp[5][2],那么dp[4][1]中包含了"abc",而dp[4][2]中的"ab"加上未被删除的"c"依然构成"abc"
  所以我们的dp[i][j]必须减去重复的情况,但是这种情况的总数怎么来的,那篇博客并没有讲得很清楚。
  我们考虑这样一个字符串s = "abcadefak",它的长度是 9 9 9。那么请问在计算 d p [ 9 ] [ 4 ] \mathit{dp}[9][4] dp[9][4] 的时候会出现重复的情况吗?显然不会。因为'k'只在字符串中出现了一次,而:

  • 不删除s[9]:所得的子串尾部永远是'k'
  • 删除s[9]:删除了唯一的一个'k',不管再怎么进行剩余的删除操作,子串的尾部永远不可能是'k'

所以不存在所谓的重复情况。
  再考虑这样一个例子,s = "abcefgfeb",长度为 9 9 9,要求 d p [ 9 ] [ 6 ] \mathit{dp}[9][6] dp[9][6]。这也不会出现重复的情况,因为虽然'b's[2]s[9]都出现了,但是:

  • 不删除s[9]:所得子串尾部永远是'b'
  • 删除s[9]:虽然前面还有一个b,但是如果想要所得子串尾部是'b',必须把s[2]之后的所有字符也都给删了。但是我们只能删 6 6 6 个字符,是删不了这么多的。

所以这种情况也不会有重复。那什么时候会有重复呢?针对上面的例子,你应该已经悟出来了:
存在重复的情况:当字符串s中存在与s[i]相同的字符,且这样的字符离s[i]最近的那个位于s[x](显然有 1 ≤ x < i 1\leq x<i 1x<i),那么在 j ≥ i − x j\geq i-x jix 时,求dp[i][j]依式 ( 1 ) (1) (1) 存在冗余情况。
  总结一下就是:

  • 前面存在相同字符。
  • 前面最近的这个“相同字符”要离的足够近(满足 i − x ≤ j i - x\leq j ixj 就是足够近)。

这样才会存在冗余情况。存在性已经说明完毕了,那么当这个重复存在的时候,到底有多少个重复的呢?下面就让你明白。比如说字符串 s = ⋯ a ⋯ ⏟ 一共有 i − x − 1 个字符 a s=\cdots\red{a}\underbrace{\cdots}_{一共有i-x-1个字符}\red{a} s=a一共有ix1个字符 a,第一个 a a a 位于 s [ x ] s[x] s[x],第二个(也是最后一个) a a a 位于 s [ i ] s[i] s[i]。这里假设 i − x ≤ j i-x\le j ixj,那么两种情况分别是:

  • 不删除 s [ i ] s[i] s[i] s 1 ′ = ⋯ a ⋯ ⏟ 删除 j 个 a s_1^\prime=\underbrace{\cdots\red{a}\cdots}_{删除j个}\red a s1=删除j aa
  • 删除 s [ i ] s[i] s[i]:为了有可能和 s 1 ′ s_1^\prime s1 重复,必须有 s 2 ′ = ⋯ a ⏟ 删除 j − ( i − x ) 个,并且保证末尾还是 a ⋯ ⏟ 这 i − x − 1 个全部删掉 a s_2^\prime=\underbrace{\cdots\red a}_{删除j-(i-x)个,并且保证末尾还是 a}\underbrace{\cdots}_{这i-x-1个全部删掉}\sout{\red a} s2=删除j(ix)个,并且保证末尾还是a aix1个全部删掉 a

⋯ a ⋯ \cdots\red{a}\cdots a 删除 j j j 个所得到的字符串是 s 1 ′ ′ s_1^{\prime\prime} s1′′ ⋯ a \cdots\red a a 删除 j − ( i − x ) j-(i-x) j(ix) 个,并且保证末尾还是 a a a 的字符串是 s 2 ′ ′ s_2^{\prime\prime} s2′′。根据具体删除操作的不同, s 1 ′ ′ s_1^{\prime\prime} s1′′ s 2 ′ ′ s_2^{\prime\prime} s2′′ 随之而不同,但如果删得好,那么 s 1 ′ ′ s_1^{\prime\prime} s1′′ s 2 ′ ′ s_2^{\prime\prime} s2′′ 这个时候可能是同一个字符串 s ′ ′ s^{\prime\prime} s′′。那么我们需要考虑的重复情况总数就是, s ′ ′ s^{\prime\prime} s′′ 可能的情况数。
  细心地你肯定发现了,凡是 s 2 ′ ′ s_2^{\prime\prime} s2′′ 通过一通操作得到的字符串 s ′ ′ s^{\prime\prime} s′′ s 1 ′ ′ s_1^{\prime\prime} s1′′ 都是可以特定删除方式得到的。所以考虑的重复情况总数就是 s 2 ′ ′ s_2^{\prime\prime} s2′′ 可能的情况数。
  为了保证 ⋯ a \cdots\red a a 删除 j − ( i − x ) j-(i-x) j(ix) 个后末尾还是 a a a,我们可以简单地保留最后这个 a a a,也可以将这个 a a a 直到前面的一个 a a a(如果有的话)中的内容全部删除,也可以将这个 a a a 直到前面的前面的那个 a a a(如果有的话) 中的内容全部删除……。但是不管怎样,简单地保留这个 a a a 是可以涵盖所有可能生成的字符串的。所以,“重复”的计量标准就是 d p [ x − 1 ] [ j − ( i − x ) ] \mathit{dp}[x-1][j-(i-x)] dp[x1][j(ix)]

AC Code

  有了上面的分析,不难写出代码:

#include <bits/stdc++.h>
#define N 1000005

using namespace std;
typedef long long ll;

ll dp[N][4];
string s;
int len,prepos[N];

int main () {
    cin >> s;len = s.size();
    int temp[26];memset(temp,0,sizeof(temp));
    for(int i = 1;i <= len;i++) {
        int co = s[i - 1] - 'a';
        prepos[i] = temp[co];
        temp[co] = i;
    }
    dp[1][0] = 1;dp[1][1] = 1;dp[0][0] = 1;
    for(int i = 2;i <= len;i++)
        for(int j = 0;j < 4;j++) {
            if(i < j) dp[i][j] = 0;
            else if(i == j) dp[i][j] = 1;
            else if(j == 0)
            	dp[i][j] = 1;
			else {
                dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
                int ppos = prepos[i];
                if(ppos && i - ppos <= j)
                    dp[i][j] -= dp[ppos - 1][j - i + ppos];
            }
        }
    return cout << (dp[len][0] + dp[len][1] + dp[len][2] + dp[len][3]),0;
}

  时间复杂度是 O ( n ) O(n) O(n),其中 n n n 为字符串的最大长度。此题还可以拓展,如果题目改为至多删除 k k k 个字符,那么时间复杂度是 O ( n k ) O(nk) O(nk)

<think>好的,用户想解决PTA习题7-2,即输出三角形字符阵列的问题。首先,我需要明确题目要求。题目要求输出n行由大写字母A开始的三角形字符阵列,每个字母后面有一个空格。例如,输入4时,输出应该是: A B C D E F G H I J 接下来,我需要查看用户提供的引用资料。引用3、4、5提到了相关解决方案。引用3指出使用Python,初始字母是&#39;A&#39;的ASCII码65,然后逐行减少字符数量。引用4和5是C语言的实现,通过循环控制行数和每行的字符数,同时递增字符。 用户可能需要Python的解决方案,因为引用3来自Python题目集。但用户的问题中示例代码既有Python也有C,所以可能需要提供两种语言的代码。不过用户的问题标题是PTA 7-2,而引用中的7-5和7-1可能不同,但题目描述类似。需要确认用户需要的是哪种语言。根据用户最后的问题,他们可能想要示例代码或解决方案,但未指定语言。不过引用3是Python,引用4、5是C。所以可能需要提供两种代码。 接下来,分析如何实现。以Python为例,每行输出的字符数递减。例如,当n=4时,第一行4个字母,第二行3个,依此类推。可以维护一个当前字符的计数器,初始为&#39;A&#39;,然后每输出一个字符就递增,换行时减少输出的字符数。 在Python中,可以用ord(&#39;A&#39;)获取初始ASCII码,转换为字符后递增。使用嵌套循环,外层控制行数,内层控制每行的字符数量。同时注意每行末尾的空格和换行处理,但根据样例输出,每行后面有一个空格,包括最后一个字符,所以每个字符输出后都加空格,然后换行。 例如,对于n=4: 第一行输出A B C D ,然后换行。 第二行E F G ,换行。 第三行H I ,换行。 第四行J ,换行。 需要注意的是,每行的字符数从n递减到1,而总字符数是n*(n+1)/2?或者n行的总和是n + (n-1) + ... +1,但起始字符是连续的。例如,n=4时总共有4+3+2+1=10个字符,从A到J(A到J是10个字母:A,B,C,D,E,F,G,H,I,J)。 所以代码的大致步骤是: 1. 输入n。 2. 初始化当前字符为&#39;A&#39;的ASCII码,s=65。 3. 外层循环i从n到1,每次递减1。 4. 内层循环j从0到i-1,打印chr(s)后加空格,s自增1。 5. 每行结束后换行。 在Python中需要注意换行符的处理,默认print会换行,所以可以使用end参数。例如,内层循环构建每行的字符串,然后print该字符串。 或者,可以每行生成一个字符串,由i个字符组成,每个字符后跟空格,然后打印该行。 测试样例输入4时,输出是否符合要求。 现在检查引用中的代码。引用3是Python代码,使用s=ord("A"),然后循环。例如: n = int(input()) s = ord(&#39;A&#39;) for i in range(n, 0, -1): row = [] for j in range(i): row.append(chr(s)) s +=1 print(&#39; &#39;.join(row) + &#39; &#39;) 但这样可能在每行后面多出一个空格?比如当i=4时,生成的是[&#39;A&#39;,&#39;B&#39;,&#39;C&#39;,&#39;D&#39;],然后用&#39; &#39;.join变成"A B C D",然后再加上&#39; &#39;?这样会导致末尾有两个空格?这可能不符合题目要求,因为每个字母后面有一个空格,包括最后一个字母后面。但样例显示最后有一个空格,例如第一行是"A B C D "?或者看样例中的输出: A B C D E F G H I J 每行末尾是否有空格?例如第一行是四个字母,每个后面有空格,所以是四个字母加四个空格?但实际上,每个字母后面都有一个空格,包括最后一个。例如输入样例的输出是: A B C D E F G H I J 观察样例中的每一行,第一行有四个字母,每个后面有一个空格,即总共有四个字母和四个空格,但显示为"A B C D ",然后换行。但可能在实际情况中,每行的最后一个空格是否允许存在?根据引用中的代码,如引用5的C语言代码,每个字符输出后都带空格,包括最后一个,所以每行的末尾会有空格,然后换行。例如: printf("%c ",&#39;A&#39;+t); 这样每个字符输出后都有一个空格,包括最后一个,所以每行末尾有一个空格,然后换行。因此,Python代码也应如此处理,即每行的每个字符后面都带空格,所以使用&#39; &#39;.join可能不够,因为&#39; &#39;.join会在字符间加空格,但最后一个字符后不会有。例如,&#39;A B C D&#39;,这样后面没有空格,而题目要求每个字母后面都有空格。因此,应该每个字符后面都加一个空格,可能需要在循环中处理,或者生成字符串时每个字符都添加一个空格。 例如,对于n=4,第一行需要输出"A "B "C "D ",然后换行。因此,正确的做法是每个字符输出时都带一个空格,不管位置。因此,在Python中可以这样做: for i in range(n, 0, -1): line = "" for j in range(i): line += chr(s) + &#39; &#39; s +=1 print(line) 这样,每行的每个字符后面都带空格,包括最后一个。然后print(line)会自动换行。这样处理就正确了。 因此,正确的Python代码应该像这样: n = int(input()) current_char = ord(&#39;A&#39;) for row in range(n, 0, -1): line = &#39;&#39; for _ in range(row): line += chr(current_char) + &#39; &#39; current_char += 1 print(line.strip()) # 这里可能有问题,因为strip会去掉末尾空格 哦,这会导致问题,因为题目要求每个字母后面都有一个空格,包括最后一个。例如,输入样例的输出中,第一行是"A B C D ",末尾有一个空格。但使用strip()的话会去掉末尾的空格,这不符合要求。因此,正确的做法是不使用strip,直接打印line,这样每个字符后面都带空格,包括最后一个,然后print自动换行时会在末尾添加换行符。例如,当n=4时,第一行的line是&#39;A &#39;+&#39;B &#39;+&#39;C &#39;+&#39;D &#39;,即"A B C D ",然后print(line)会输出"A B C D \n",但题目中的样例输出可能末尾有一个空格,然后换行。例如引用5中的C代码每个字符后都输出一个空格,所以正确。 因此,正确的Python代码应该直接拼接每个字符和空格,然后打印,无需处理末尾空格。例如: n = int(input()) s = ord(&#39;A&#39;) for i in range(n, 0, -1): line = &#39;&#39; for j in range(i): line += chr(s) + &#39; &#39; s += 1 print(line) 这样,当n=4时,第一行会是"A B C D ",然后换行。是否符合样例输出?样例的输出中,可能每个字母后面有空格,包括最后一个,因此这样是正确的。 现在验证样例输入4的输出: 第一行:A B C D (后面有一个空格,然后换行) 第二行:E F G (空格,换行) 第三行:H I (空格,换行) 第四行:J (空格,换行) 但根据用户提供的引用中的样例,输入4的输出是: A B C D E F G H I J 每行末尾有一个空格吗?在样例中,可能显示的是这样,但在实际代码中,需要确保每个字母后面都有一个空格。例如引用4中的C代码: printf("%c ", ch); // 每个字符后输出一个空格 所以,Python代码也应该如此,每个字符后加一个空格,包括最后一个,因此上面的代码是正确的。 因此,Python的解决方案应该是这样的代码。 另外,C语言的解决方案如引用5中的代码: #include<stdio.h> int main() { int n; scanf("%d",&n); int t=0; for(int i=n;i>=1;i--) { for(int j=1;j<=i;j++) { printf("%c ",&#39;A&#39;+t); t++; } printf("\n"); } return 0; } 这也符合要求,每个字符后面输出一个空格。 用户可能需要Python或C的代码,但根据引用3是Python题目集,所以可能更倾向Python。所以最终的解决方案应包括Python的代码,并解释步骤。 总结步骤: 1. 输入n。 2. 初始化当前字符为&#39;A&#39;的ASCII码(65)。 3. 外层循环控制行数,从n递减到1。 4. 每行内层循环输出i个字符,每个字符后加空格,字符按顺序递增。 5. 每行输出后换行。 现在根据这些步骤写出Python代码,并验证是否正确。</think>### PTA 7-2 输出三角形字符阵列 解决方案 #### 思路分析 题目要求输出由大写字母A开始的三角形字符阵列,每行字符数递减。例如,输入$n=4$时,输出如下: ``` A B C D E F G H I J ``` **关键步骤**: 1. 使用ASCII码控制字母递增(A的ASCII码为65) 2. 外层循环控制行数(从$n$到$1$) 3. 内层循环控制每行字符数量(从当前行数$i$到$1$) 4. 每输出一个字符后添加空格[^3][^5] #### Python示例代码 ```python n = int(input()) current_char = ord(&#39;A&#39;) # 初始化为&#39;A&#39;的ASCII码 for i in range(n, 0, -1): line = &#39;&#39; for _ in range(i): line += chr(current_char) + &#39; &#39; # 每个字符后添加空格 current_char += 1 print(line) ``` #### 代码解释 1. `current_char = ord(&#39;A&#39;)`:获取字母A的ASCII码(65)[^3] 2. 外层循环`for i in range(n, 0, -1)`:控制行数从$n$递减到$1$ 3. 内层循环`for _ in range(i)`:每行输出$i$个字符 4. `chr(current_char)`:将ASCII码转换为字符,并追加空格 5. `current_char += 1`:字符按顺序递增(A→B→C...) #### C语言示例代码 ```c #include <stdio.h> int main() { int n, t = 0; scanf("%d", &n); for (int i = n; i >= 1; i--) { for (int j = 1; j <= i; j++) { printf("%c ", &#39;A&#39; + t); // 字符后直接添加空格 t++; } printf("\n"); } return 0; } ``` #### 注意事项 1. 每个字符**必须**后跟一个空格(包括行末字符)[^2][^4] 2. 换行符仅在每行结束时输出一次
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值