洛谷普及B3703 [语言月赛202301] 新年快乐

题目:[语言月赛202301] 新年快乐

题号:B3703

难度:普及一


题目分析

题目背景

要过春节了,扶苏收到了她的新年礼物:一个崭新的字符串。

题目描述

扶苏收到的字符串是一个只含小写字母的字符串 s。

对于一个长度为 x 的只含小写字母的字符串 t,我们定义 t 的『上一个字符串』是:将所有的长度为 x 且仅含小写字母的字符串按字典序排列后,恰好排在 t 之前一个的字符串。

例如,字符串 bcd 的『上一个字符串』是 bcc,而 aaa 的『上一个字符串』不存在。

现在,扶苏有 q 次询问,每次询问给出一个区间 [l,r],查询:s 的第 l 个到第 r 个字符组成的字符串的『上一个字符串』是否在 s 中出现?

如果查询区间对应的字符串不存在『上一个字符串』,也算作『上一个字符串』没有在 s 中出现。

输入格式

第一行是一个字符串,表示扶苏收到的字符串 s。
第二行有一个整数,表示询问的次数 q。
接下来 q 行,每行两个整数,表示一次询问的 l,r。

输出格式

对于每次询问,输出两行,每行一个字符串。
对于每次询问的第一行输出,请输出对应字符串的『上一个字符串』。如果『上一个字符串』不存在,则输出 NULL
对于每次询问的第二行输出,如果对应字符串的『上一个字符串』在 s 中出现了,请输出 Happy New Year!,否则请输出 Happy Chinese New Year!

分析

本题主要意思,通过输入获取一个父字符串,和若干个范围

然后分别找出这些范围内的子字符串的“上一个字符串”(字典序减一)

找到之后访问父字符串中,比较是否存在该字符串。

然后不同的条件对应不同的输出,

bcc
Happy New Year!   存在指定字典序减一字符串,并且也在父字符串中找到了该字符串
NULL
Happy Chinese New Year!存在指定字典序减一字符串,但是未在父字符串中找到了该字符串
zy
Happy Chinese New Year!不存在指定字典序减一字符串(例如aaa的减一为zzz)

分为上述三种情况。

思路

 本题代码量是肯定不会少的,

因为我们需要,在获取的父字符串中,获取指定范围的子字符串,然后计算出该子字符串的字典序减一的上一个字符串,然后再在父字符串中找寻该字符串,再对应不同的输出。

我们能做的是,尽可能的将其分解为一个个简单的子任务,将程序尽可能的模块化,这样不仅方便调试,而且思路清晰。

我们需要的写的函数

1,在父字符串中根据传入的范围输出该范围的子字符串

2,将获得的子字符串字典序减一,得到“前一个字符串”

3,传入父字符串和计算得到的指定字符串,判断是否在父字符串中存在。

此外我们还要在main函数中做好枚举和条件判断,输入和输出。

此题的数据量并不大,所以本题不用采用动态的内存分配,只需要提前设定最大范围的数组作为储存中间子字符串的传递。

代码

首先是函数一:获取指定范围的子字符串(其中p是全局变量中的缓存数组)

char *ptr(char a[],int left, int right) {//取出指定范围字符串
    //left和right都是下标
    pn++;
    int u = 0;
    for(int i = left; i <= right; i++) {
        p[pn][u++] = a[i-1]; 
    }
p[pn][u] = '\0';
return p[pn];
}

函数二:获取指定字符串的前一个字符串(此函数的形参是上一个函数的范围值)

char *ptr1(char b[])
{
    int n = strlen(b);
//此时b[n]指向空字符
while(n!=0)
{
    n--; //第一次指向的是最后一个字符
if(b[n] != 'a')
 {b[n] = b[n] - 1;
break; 
}
else
b[n] = 'z';
}
return b;//如果全是a,那么就返回全是z的字符串,则指定未找到
}

函数三:判断特定字符串是否在父字符串中存在

int find(char a[], char b[]) {
    //a是主串,b是子串
    int n = strlen(a);
    int m = strlen(b);
    for(int i = 0; i <= n - m; i++) {
        int j;
        for(j = 0; j < m; j++) {
            if(a[i+j] != b[j]) break;
        }
        if(j == m) return 1; //找到子串,返回子串的起始位置
    } 
    return 0; //未找到子串,返回-1
}

主代码

#include <stdio.h>

struct stu{ //存储指定范围的结构体一维数组
int left;
int right;
}ctr[202];
char s[402];//存储输入的字符串
char p[402][402];//存储所有的子串
char q[402][402];//存储所有的子串的字典序-1的子串
int pn = 0; //记录p数组的行数
char *ptr(char a[],int left, int right) {//取出指定范围字符串
    //left和right都是下标
    pn++;
    int u = 0;
    for(int i = left; i <= right; i++) {
        p[pn][u++] = a[i-1]; 
    }
p[pn][u] = '\0';
return p[pn];
}
char *ptr1(char b[]) //获取字典序减一的上一个字符串
{
    int n = strlen(b);
//此时b[n]指向空字符
while(n!=0)
{
    n--; //第一次指向的是最后一个字符
if(b[n] != 'a')
 {b[n] = b[n] - 1;
break; 
}
else
b[n] = 'z';
}
return b;//如果全是a,那么就返回全是z的字符串,则指定未找到
}
int find(char a[], char b[]) {
    //a是主串,b是子串
    int n = strlen(a);
    int m = strlen(b);
    for(int i = 0; i <= n - m; i++) {
        int j;
        for(j = 0; j < m; j++) {
            if(a[i+j] != b[j]) break;
        }
        if(j == m) return 1; //找到子串,返回子串的起始位置
    } 
    return 0; //未找到子串,返回-1
}


int main() {
    int y;
fgets(s, 402, stdin);
int n = strlen(s);
if(s[n-1] == '\n') s[n-1] = '\0';
int r = strlen(s) - 1; //最后一个字符的下标(‘\0’前面的那个)
scanf("%d", &y);
for(int i = 1; i <= y; i++)
    scanf("%d %d", &ctr[i].left, &ctr[i].right);
//访问的时候记得下标是从1开始的 1 3  代表第一个字符到第三个字符
for(int i = 1; i <= y; i++) {
    int left = ctr[i].left; //第一个字符的下标
    int right = ctr[i].right; //最后一个字符的下标
    char *str = ptr(s, left, right); //取出指定范围字符串
    char *str1 = ptr1(str); //取出字典序-1的字符串
    int ret = 1;
    for(int j=0;j<strlen(str1);j++)
    if(str1[j]!='z')
    ret = 0;
    if(ret)
    {
        printf("NULL\nHappy Chinese New Year!\n");
        continue; //如果全是z,那么就输出NULL和Happy Chinese New Year!,并且跳过后面的操作,进入下一轮循环
        //注意这里是continue,不是break,因为我们要跳过后面的操作,进入下一轮循环,而不是跳出循环,所以是continue而不是break
    }
    int flag = find(s, str1); //查找字典序-1的字符串是否在主串中
    printf("%s\n", str1);
    if(flag) printf("Happy New Year!\n"); //如果找到,输出
    else printf("Happy Chinese New Year!\n"); //如果未找到,输出

}

}

总结

本题思路逻辑上不难,但还是很具有挑战性的,因为要写的代码块不少,将复杂的问题简化成多个简单的子问题是解决本题的关键,当然本题也有很多可以优化的地方,比如,如果对传入传出的指针参数运用熟练,且不怕出错后的复杂调试的话,完全可以不用声明这么多的缓存数组,可以直接将每一步函数得到的指针参数(字符串首地址)直接互相传送。不过这些都不重要,重要的是模块化解决每个规模逐渐增大的题,而且要注意好代码的时间空间复杂度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LAOLONG-C

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值